1//////////////////////////////////////////////////////////////////////
   2// LibFile: shapes3d.scad
   3//   Some standard modules for making 3d shapes with attachment support, and function forms
   4//   that produce a VNF.  Also included are shortcuts cylinders in each orientation and extended versions of
   5//   the standard modules that provide roundovers and chamfers.  The spheroid() module provides
   6//   several different ways to make a sphere, and the text modules let you write text on a path
   7//   so you can place it on a curved object.  A ruler lets you measure objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Attachable cubes, cylinders, spheres, ruler, and text.  Many can produce a VNF.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15use <builtins.scad>
  16
  17
  18// Section: Cuboids, Prismoids and Pyramids
  19
  20// Function&Module: cube()
  21// Synopsis: Creates a cube with anchors for attaching children.
  22// SynTags: Geom, VNF, Ext
  23// Topics: Shapes (3D), Attachable, VNF Generators
  24// See Also: cuboid(), prismoid()
  25// Usage: As Module (as in native OpenSCAD)
  26//   cube(size, [center]);
  27// Usage: With BOSL2 Attachment extensions
  28//   cube(size, [center], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
  29// Usage: As Function (BOSL2 extension)
  30//   vnf = cube(size, ...);
  31// Description:
  32//   Creates a 3D cubic object.
  33//   This module extends the built-in cube()` module by providing support for attachments and a function form.  
  34//   When called as a function, returns a [VNF](vnf.scad) for a cube.
  35// Arguments:
  36//   size = The size of the cube.
  37//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=FRONT+LEFT+BOTTOM`.
  38//   ---
  39//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
  40//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
  41//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
  42// Example: Simple cube.
  43//   cube(40);
  44// Example: Rectangular cube.
  45//   cube([20,40,50]);
  46// Example: Anchoring.
  47//   cube([20,40,50], anchor=BOTTOM+FRONT);
  48// Example: Spin.
  49//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
  50// Example: Orientation.
  51//   cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
  52// Example: Standard Connectors.
  53//   cube(40, center=true) show_anchors();
  54// Example: Called as Function
  55//   vnf = cube([20,40,50]);
  56//   vnf_polyhedron(vnf);
  57
  58module cube(size=1, center, anchor, spin=0, orient=UP)
  59{
  60    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
  61    size = scalar_vec3(size);
  62    attachable(anchor,spin,orient, size=size) {
  63        _cube(size, center=true);
  64        children();
  65    }
  66}
  67
  68function cube(size=1, center, anchor, spin=0, orient=UP) =
  69    let(
  70        siz = scalar_vec3(size)
  71    )
  72    assert(all_positive(siz), "All size components must be positive.")
  73    let(
  74        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
  75        unscaled = [
  76            [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
  77            [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
  78        ]/2,
  79        verts = is_num(size)? unscaled * size :
  80            is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
  81            assert(is_num(size) || is_vector(size,3)),
  82        faces = [
  83            [0,1,2], [0,2,3],  //BOTTOM
  84            [0,4,5], [0,5,1],  //FRONT
  85            [1,5,6], [1,6,2],  //RIGHT
  86            [2,6,7], [2,7,3],  //BACK
  87            [3,7,4], [3,4,0],  //LEFT
  88            [6,4,7], [6,5,4]   //TOP
  89        ]
  90    ) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
  91
  92
  93
  94// Module: cuboid()
  95// Synopsis: Creates a cube with chamfering and roundovers.
  96// SynTags: Geom
  97// Topics: Shapes (3D), Attachable, VNF Generators
  98// See Also: prismoid(), rounded_prism()
  99// Usage: Standard Cubes
 100//   cuboid(size, [anchor=], [spin=], [orient=]);
 101//   cuboid(size, p1=, ...);
 102//   cuboid(p1=, p2=, ...);
 103// Usage: Chamfered Cubes
 104//   cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...);
 105// Usage: Rounded Cubes
 106//   cuboid(size, [rounding=], [teardrop=], [edges=], [except=], [trimcorners=], ...);
 107// Usage: Attaching children
 108//   cuboid(...) ATTACHMENTS;
 109//
 110// Description:
 111//   Creates a cube or cuboid object, with optional chamfering or rounding of edges and corners.
 112//   You cannot mix chamfering and rounding: just one edge treatment with the same size applies to all selected edges.
 113//   Negative chamfers and roundings can be applied to create external fillets, but they
 114//   only apply to edges around the top or bottom faces.  If you specify an edge set other than "ALL"
 115//   with negative roundings or chamfers then you will get an error.  See [Specifying Edges](attachments.scad#section-specifying-edges)
 116//   for information on how to specify edge sets.
 117// Arguments:
 118//   size = The size of the cube, a number or length 3 vector.
 119//   ---
 120//   chamfer = Size of chamfer, inset from sides.  Default: No chamfering.
 121//   rounding = Radius of the edge rounding.  Default: No rounding.
 122//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: all edges.
 123//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#section-specifying-edges).  Default: No edges.
 124//   trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet.  Default: `true`
 125//   teardrop = If given as a number, rounding around the bottom edge of the cuboid won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
 126//   p1 = Align the cuboid's corner at `p1`, if given.  Forces `anchor=FRONT+LEFT+BOTTOM`.
 127//   p2 = If given with `p1`, defines the cornerpoints of the cuboid.
 128//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 129//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 130//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 131// Example: Simple regular cube.
 132//   cuboid(40);
 133// Example: Cube with minimum cornerpoint given.
 134//   cuboid(20, p1=[10,0,0]);
 135// Example: Rectangular cube, with given X, Y, and Z sizes.
 136//   cuboid([20,40,50]);
 137// Example: Cube by Opposing Corners.
 138//   cuboid(p1=[0,10,0], p2=[20,30,30]);
 139// Example: Chamferred Edges and Corners.
 140//   cuboid([30,40,50], chamfer=5);
 141// Example: Chamferred Edges, Untrimmed Corners.
 142//   cuboid([30,40,50], chamfer=5, trimcorners=false);
 143// Example: Rounded Edges and Corners
 144//   cuboid([30,40,50], rounding=10);
 145// Example(VPR=[100,0,25],VPD=180): Rounded Edges and Corners with Teardrop Bottoms
 146//   cuboid([30,40,50], rounding=10, teardrop=true);
 147// Example: Rounded Edges, Untrimmed Corners
 148//   cuboid([30,40,50], rounding=10, trimcorners=false);
 149// Example: Chamferring Selected Edges
 150//   cuboid(
 151//       [30,40,50], chamfer=5,
 152//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 153//       $fn=24
 154//   );
 155// Example: Rounding Selected Edges
 156//   cuboid(
 157//       [30,40,50], rounding=5,
 158//       edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
 159//       $fn=24
 160//   );
 161// Example: Negative Chamferring
 162//   cuboid(
 163//       [30,40,50], chamfer=-5,
 164//       edges=[TOP,BOT], except=RIGHT,
 165//       $fn=24
 166//   );
 167// Example: Negative Chamferring, Untrimmed Corners
 168//   cuboid(
 169//       [30,40,50], chamfer=-5,
 170//       edges=[TOP,BOT], except=RIGHT,
 171//       trimcorners=false, $fn=24
 172//   );
 173// Example: Negative Rounding
 174//   cuboid(
 175//       [30,40,50], rounding=-5,
 176//       edges=[TOP,BOT], except=RIGHT,
 177//       $fn=24
 178//   );
 179// Example: Negative Rounding, Untrimmed Corners
 180//   cuboid(
 181//       [30,40,50], rounding=-5,
 182//       edges=[TOP,BOT], except=RIGHT,
 183//       trimcorners=false, $fn=24
 184//   );
 185// Example: Roundings and Chamfers can be as large as the full size of the cuboid, so long as the edges would not interfere.
 186//   cuboid([40,20,10], rounding=20, edges=[FWD+RIGHT,BACK+LEFT]);
 187// Example: Standard Connectors
 188//   cuboid(40) show_anchors();
 189
 190module cuboid(
 191    size=[1,1,1],
 192    p1, p2,
 193    chamfer,
 194    rounding,
 195    edges=EDGES_ALL,
 196    except=[],
 197    except_edges,
 198    trimcorners=true,
 199    teardrop=false,
 200    anchor=CENTER,
 201    spin=0,
 202    orient=UP
 203) {
 204    module trunc_cube(s,corner) {
 205        multmatrix(
 206            (corner.x<0? xflip() : ident(4)) *
 207            (corner.y<0? yflip() : ident(4)) *
 208            (corner.z<0? zflip() : ident(4)) *
 209            scale(s+[1,1,1]*0.001) *
 210            move(-[1,1,1]/2)
 211        ) polyhedron(
 212            [[1,1,1],[1,1,0],[1,0,0],[0,1,1],[0,1,0],[1,0,1],[0,0,1]],
 213            [[0,1,2],[2,5,0],[0,5,6],[0,6,3],[0,3,4],[0,4,1],[1,4,2],[3,6,4],[5,2,6],[2,4,6]]
 214        );
 215    }
 216    module xtcyl(l,r) {
 217        if (teardrop) {
 218            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN);
 219        } else {
 220            yrot(90) cyl(l=l, r=r);
 221        }
 222    }
 223    module ytcyl(l,r) {
 224        if (teardrop) {
 225            teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN);
 226        } else {
 227            zrot(90) yrot(90) cyl(l=l, r=r);
 228        }
 229    }
 230    module tsphere(r) {
 231        if (teardrop) {
 232            onion(r=r, cap_h=r, ang=teardrop, orient=DOWN);
 233        } else {
 234            spheroid(r=r, style="octa", orient=DOWN);
 235        }
 236    }
 237    module corner_shape(corner) {
 238        e = _corner_edges(edges, corner);
 239        cnt = sum(e);
 240        r = first_defined([chamfer, rounding]);
 241        dummy = assert(is_finite(r) && !approx(r,0));
 242        c = [r,r,r];
 243        m = 0.01;
 244        c2 = v_mul(corner,c/2);
 245        c3 = v_mul(corner,c-[1,1,1]*m/2);
 246        $fn = is_finite(chamfer)? 4 : quantup(segs(r),4);
 247        translate(v_mul(corner, size/2-c)) {
 248            if (cnt == 0 || approx(r,0)) {
 249                translate(c3) cube(m, center=true);
 250            } else if (cnt == 1) {
 251                if (e.x) {
 252                    right(c3.x) {
 253                        intersection() {
 254                            xtcyl(l=m, r=r);
 255                            multmatrix(
 256                                (corner.y<0? yflip() : ident(4)) *
 257                                (corner.z<0? zflip() : ident(4))
 258                            ) {
 259                                yrot(-90) linear_extrude(height=m+0.1, center=true) {
 260                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 261                                }
 262                            }
 263                        }
 264                    }
 265                } else if (e.y) {
 266                    back(c3.y) {
 267                        intersection() {
 268                            ytcyl(l=m, r=r);
 269                            multmatrix(
 270                                (corner.x<0? xflip() : ident(4)) *
 271                                (corner.z<0? zflip() : ident(4))
 272                            ) {
 273                                xrot(90) linear_extrude(height=m+0.1, center=true) {
 274                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 275                                }
 276                            }
 277                        }
 278                    }
 279                } else if (e.z) {
 280                    up(c3.z) {
 281                        intersection() {
 282                            zcyl(l=m, r=r);
 283                            multmatrix(
 284                                (corner.x<0? xflip() : ident(4)) *
 285                                (corner.y<0? yflip() : ident(4))
 286                            ) {
 287                                linear_extrude(height=m+0.1, center=true) {
 288                                    polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
 289                                }
 290                            }
 291                        }
 292                    }
 293                }
 294            } else if (cnt == 2) {
 295                intersection() {
 296                    if (!e.x) {
 297                        intersection() {
 298                            ytcyl(l=c.y*2, r=r);
 299                            zcyl(l=c.z*2, r=r);
 300                        }
 301                    } else if (!e.y) {
 302                        intersection() {
 303                            xtcyl(l=c.x*2, r=r);
 304                            zcyl(l=c.z*2, r=r);
 305                        }
 306                    } else {
 307                        intersection() {
 308                            xtcyl(l=c.x*2, r=r);
 309                            ytcyl(l=c.y*2, r=r);
 310                        }
 311                    }
 312                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 313                }
 314            } else {
 315                intersection() {
 316                    if (trimcorners) {
 317                        tsphere(r=r);
 318                    } else {
 319                        intersection() {
 320                            xtcyl(l=c.x*2, r=r);
 321                            ytcyl(l=c.y*2, r=r);
 322                            zcyl(l=c.z*2, r=r);
 323                        }
 324                    }
 325                    translate(c2) trunc_cube(c,corner); // Trim to just the octant.
 326                }
 327            }
 328        }
 329    }
 330
 331    size = scalar_vec3(size);
 332    edges = _edges(edges, except=first_defined([except_edges,except]));
 333    teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop;
 334    chamfer = approx(chamfer,0) ? undef : chamfer;
 335    rounding = approx(rounding,0) ? undef : rounding;
 336    checks =
 337        assert(is_vector(size,3))
 338        assert(all_nonnegative(size), "All components of size= must be >=0")
 339        assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value")
 340        assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value")
 341        assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding")
 342        assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<=90), "teardrop must be either false or an angle number between 0 and 90")
 343        assert(is_undef(p1) || is_vector(p1))
 344        assert(is_undef(p2) || is_vector(p2))
 345        assert(is_bool(trimcorners));
 346    if (!is_undef(p1)) {
 347        if (!is_undef(p2)) {
 348            translate(pointlist_bounds([p1,p2])[0]) {
 349                cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 350            }
 351        } else {
 352            translate(p1) {
 353                cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
 354            }
 355        }
 356    } else {
 357        rr = max(default(chamfer,0), default(rounding,0));
 358        if (rr>0) {
 359            minx = max(
 360                edges.y[0] + edges.y[1], edges.y[2] + edges.y[3],
 361                edges.z[0] + edges.z[1], edges.z[2] + edges.z[3],
 362                edges.y[0] + edges.z[1], edges.y[0] + edges.z[3],
 363                edges.y[1] + edges.z[0], edges.y[1] + edges.z[2],
 364                edges.y[2] + edges.z[1], edges.y[2] + edges.z[3],
 365                edges.y[3] + edges.z[0], edges.y[3] + edges.z[2]
 366            ) * rr;
 367            miny = max(
 368                edges.x[0] + edges.x[1], edges.x[2] + edges.x[3],
 369                edges.z[0] + edges.z[2], edges.z[1] + edges.z[3],
 370                edges.x[0] + edges.z[2], edges.x[0] + edges.z[3],
 371                edges.x[1] + edges.z[0], edges.x[1] + edges.z[1],
 372                edges.x[2] + edges.z[2], edges.x[2] + edges.z[3],
 373                edges.x[3] + edges.z[0], edges.x[3] + edges.z[1]
 374            ) * rr;
 375            minz = max(
 376                edges.x[0] + edges.x[2], edges.x[1] + edges.x[3],
 377                edges.y[0] + edges.y[2], edges.y[1] + edges.y[3],
 378                edges.x[0] + edges.y[2], edges.x[0] + edges.y[3],
 379                edges.x[1] + edges.y[2], edges.x[1] + edges.y[3],
 380                edges.x[2] + edges.y[0], edges.x[2] + edges.y[1],
 381                edges.x[3] + edges.y[0], edges.x[3] + edges.y[1]
 382            ) * rr;
 383            check =
 384                assert(minx <= size.x, "Rounding or chamfering too large for cuboid size in the X axis.")
 385                assert(miny <= size.y, "Rounding or chamfering too large for cuboid size in the Y axis.")
 386                assert(minz <= size.z, "Rounding or chamfering too large for cuboid size in the Z axis.")
 387            ;
 388        }
 389        majrots = [[0,90,0], [90,0,0], [0,0,0]];
 390        attachable(anchor,spin,orient, size=size) {
 391            if (is_finite(chamfer) && !approx(chamfer,0)) {
 392                if (edges == EDGES_ALL && trimcorners) {
 393                    if (chamfer<0) {
 394                        cube(size, center=true) {
 395                            attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 396                            attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
 397                        }
 398                    } else {
 399                        isize = [for (v = size) max(0.001, v-2*chamfer)];
 400                        hull() {
 401                            cube([ size.x, isize.y, isize.z], center=true);
 402                            cube([isize.x,  size.y, isize.z], center=true);
 403                            cube([isize.x, isize.y,  size.z], center=true);
 404                        }
 405                    }
 406                } else if (chamfer<0) {
 407                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative chamfer with Z aligned edges.");
 408                    ach = abs(chamfer);
 409                    cube(size, center=true);
 410
 411                    // External-Chamfer mask edges
 412                    difference() {
 413                        union() {
 414                            for (i = [0:3], axis=[0:1]) {
 415                                if (edges[axis][i]>0) {
 416                                    vec = EDGE_OFFSETS[axis][i];
 417                                    translate(v_mul(vec/2, size+[ach,ach,-ach])) {
 418                                        rotate(majrots[axis]) {
 419                                            cube([ach, ach, size[axis]], center=true);
 420                                        }
 421                                    }
 422                                }
 423                            }
 424
 425                            // Add multi-edge corners.
 426                            if (trimcorners) {
 427                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 428                                    ce = _corner_edges(edges, [xa,ya,za]);
 429                                    if (ce.x + ce.y > 1) {
 430                                        translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
 431                                            cube([ach+0.01,ach+0.01,ach], center=true);
 432                                        }
 433                                    }
 434                                }
 435                            }
 436                        }
 437
 438                        // Remove bevels from overhangs.
 439                        for (i = [0:3], axis=[0:1]) {
 440                            if (edges[axis][i]>0) {
 441                                vec = EDGE_OFFSETS[axis][i];
 442                                translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
 443                                    rotate(majrots[axis]) {
 444                                        zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
 445                                    }
 446                                }
 447                            }
 448                        }
 449                    }
 450                } else {
 451                    hull() {
 452                        corner_shape([-1,-1,-1]);
 453                        corner_shape([ 1,-1,-1]);
 454                        corner_shape([-1, 1,-1]);
 455                        corner_shape([ 1, 1,-1]);
 456                        corner_shape([-1,-1, 1]);
 457                        corner_shape([ 1,-1, 1]);
 458                        corner_shape([-1, 1, 1]);
 459                        corner_shape([ 1, 1, 1]);
 460                    }
 461                }
 462            } else if (is_finite(rounding) && !approx(rounding,0)) {
 463                sides = quantup(segs(rounding),4);
 464                if (edges == EDGES_ALL) {
 465                    if(rounding<0) {
 466                        cube(size, center=true);
 467                        zflip_copy() {
 468                            up(size.z/2) {
 469                                difference() {
 470                                    down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
 471                                    down(-rounding) {
 472                                        ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
 473                                        xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
 474                                    }
 475                                }
 476                            }
 477                        }
 478                    } else {
 479                        isize = [for (v = size) max(0.001, v-2*rounding)];
 480                        minkowski() {
 481                            cube(isize, center=true);
 482                            if (trimcorners) {
 483                                tsphere(r=rounding, $fn=sides);
 484                            } else {
 485                                intersection() {
 486                                    xtcyl(r=rounding, l=rounding*2, $fn=sides);
 487                                    ytcyl(r=rounding, l=rounding*2, $fn=sides);
 488                                    cyl(r=rounding, h=rounding*2, $fn=sides);
 489                                }
 490                            }
 491                        }
 492                    }
 493                } else if (rounding<0) {
 494                    checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative rounding with Z aligned edges.");
 495                    ard = abs(rounding);
 496                    cube(size, center=true);
 497
 498                    // External-Rounding mask edges
 499                    difference() {
 500                        union() {
 501                            for (i = [0:3], axis=[0:1]) {
 502                                if (edges[axis][i]>0) {
 503                                    vec = EDGE_OFFSETS[axis][i];
 504                                    translate(v_mul(vec/2, size+[ard,ard,-ard]-[0.01,0.01,0])) {
 505                                        rotate(majrots[axis]) {
 506                                            cube([ard, ard, size[axis]], center=true);
 507                                        }
 508                                    }
 509                                }
 510                            }
 511
 512                            // Add multi-edge corners.
 513                            if (trimcorners) {
 514                                for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
 515                                    ce = _corner_edges(edges, [xa,ya,za]);
 516                                    if (ce.x + ce.y > 1) {
 517                                        translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
 518                                            cube([ard+0.01,ard+0.01,ard], center=true);
 519                                        }
 520                                    }
 521                                }
 522                            }
 523                        }
 524
 525                        // Remove roundings from overhangs.
 526                        for (i = [0:3], axis=[0:1]) {
 527                            if (edges[axis][i]>0) {
 528                                vec = EDGE_OFFSETS[axis][i];
 529                                translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
 530                                    rotate(majrots[axis]) {
 531                                        cyl(l=size[axis]+2.1*ard, r=ard);
 532                                    }
 533                                }
 534                            }
 535                        }
 536                    }
 537                } else {
 538                    hull() {
 539                        corner_shape([-1,-1,-1]);
 540                        corner_shape([ 1,-1,-1]);
 541                        corner_shape([-1, 1,-1]);
 542                        corner_shape([ 1, 1,-1]);
 543                        corner_shape([-1,-1, 1]);
 544                        corner_shape([ 1,-1, 1]);
 545                        corner_shape([-1, 1, 1]);
 546                        corner_shape([ 1, 1, 1]);
 547                    }
 548                }
 549            } else {
 550                cube(size=size, center=true);
 551            }
 552            children();
 553        }
 554    }
 555}
 556
 557
 558function cuboid(
 559    size=[1,1,1],
 560    p1, p2,
 561    chamfer,
 562    rounding,
 563    edges=EDGES_ALL,
 564    except_edges=[],
 565    trimcorners=true,
 566    anchor=CENTER,
 567    spin=0,
 568    orient=UP
 569) = no_function("cuboid");
 570
 571
 572
 573// Function&Module: prismoid()
 574// Synopsis: Creates a rectangular prismoid shape with optional roundovers and chamfering.
 575// SynTags: Geom, VNF
 576// Topics: Shapes (3D), Attachable, VNF Generators
 577// See Also: cuboid(), rounded_prism(), trapezoid(), edge_profile()
 578// Usage: 
 579//   prismoid(size1, size2, [h|l|height|length], [shift], [xang=], [yang=], ...) [ATTACHMENTS];
 580// Usage: Chamfered and/or Rounded Prismoids
 581//   prismoid(size1, size2, h|l|height|length, [chamfer=], [rounding=]...) [ATTACHMENTS];
 582//   prismoid(size1, size2, h|l|height|length, [chamfer1=], [chamfer2=], [rounding1=], [rounding2=], ...) [ATTACHMENTS];
 583// Usage: As Function
 584//   vnf = prismoid(...);
 585// Description:
 586//   Creates a rectangular prismoid shape with optional roundovers and chamfering.
 587//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 588//   specify rounding and/or chamferring per-edge, and for top and bottom separately.
 589//   If you want to round the bottom or top edges see {{rounded_prism()}}.
 590//   .
 591//   Specification of the prismoid is similar to specification for {{trapezoid()}}.  You can specify the dimensions of the
 592//   bottom and top and its height to get a symmetric prismoid.  You can use the shift argument to shift the top face around.
 593//   You can also specify base angles either in the X direction, Y direction or both.  In order to avoid overspecification,
 594//   you may need to specify a parameter such as size2 as a list of two values, one of which is undef.  For example,
 595//   specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang.
 596// Arguments:
 597//   size1 = [width, length] of the bottom end of the prism.
 598//   size2 = [width, length] of the top end of the prism.
 599//   h/l/height/length = Height of the prism.
 600//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 601//   ---
 602//   xang = base angle in the X direction.  Can be a scalar or list of two values, one of which may be undef
 603//   yang = base angle in the Y direction.  Can be a scalar or list of two values, one of which may be undef
 604//   rounding = The roundover radius for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
 605//   rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 606//   rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 607//   chamfer = The chamfer size for the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].  Default: 0 (no chamfer)
 608//   chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 609//   chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid.  If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
 610//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 611//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 612//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 613//
 614// Example: Truncated Pyramid
 615//   prismoid(size1=[35,50], size2=[20,30], h=20);
 616// Example: Rectangular Pyramid
 617//   prismoid([40,40], [0,0], h=20);
 618// Example: Prism
 619//   prismoid(size1=[40,40], size2=[0,40], h=20);
 620// Example: Wedge
 621//   prismoid(size1=[60,35], size2=[30,0], h=30);
 622// Example: Truncated Tetrahedron
 623//   prismoid(size1=[10,40], size2=[40,10], h=40);
 624// Example: Inverted Truncated Pyramid
 625//   prismoid(size1=[15,5], size2=[30,20], h=20);
 626// Example: Right Prism
 627//   prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
 628// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
 629//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
 630// Example: Specifying bottom, height and angle
 631//   prismoid(size1=[100,75], h=30, xang=50, yang=70);
 632// Example: Specifying top, height and angle, with asymmetric angles
 633//   prismoid(size2=[100,75], h=30, xang=[50,60], yang=[70,40]);
 634// Example: Specifying top, bottom and angle for X and using that to define height.  Note that giving yang here would likely give a conflicting height calculation, which is not allowed.  
 635//   prismoid(size1=[100,75], size2=[75,35], xang=50);
 636// Example: The same as the previous example but we give a shift in Y.  Note that shift.x must be undef because you cannot give combine an angle with a shift, so a shift.x value would conflict with xang being defined.  
 637//   prismoid(size1=[100,75], size2=[75,35], xang=50, shift=[undef,20]);
 638// Example:  The X dimensions defined by the base length, angle and height; the Y dimensions defined by the top length, angle, and height. 
 639//   prismoid(size1=[100,undef], size2=[undef,75], h=30, xang=[20,90], yang=30);
 640// Example: Rounding
 641//   prismoid(100, 80, rounding=10, h=30);
 642// Example: Chamfers
 643//   prismoid(100, 80, chamfer=5, h=30);
 644// Example: Gradiant Rounding
 645//   prismoid(100, 80, rounding1=10, rounding2=0, h=30);
 646// Example: Per Corner Rounding
 647//   prismoid(100, 80, rounding=[0,5,10,15], h=30);
 648// Example: Per Corner Chamfer
 649//   prismoid(100, 80, chamfer=[0,5,10,15], h=30);
 650// Example: Mixing Chamfer and Rounding
 651//   prismoid(
 652//       100, 80, h=30,
 653//       chamfer=[0,5,0,10],
 654//       rounding=[5,0,10,0]
 655//   );
 656// Example: Really Mixing It Up
 657//   prismoid(
 658//       size1=[100,80], size2=[80,60], h=20,
 659//       chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
 660//       rounding1=[5,0,10,0], rounding2=[0,5,0,10]
 661//   );
 662// Example: How to Round a Top or Bottom Edge
 663//   diff()
 664//   prismoid([50,30], [30,20], shift=[3,6], h=15, rounding=[5,0,5,0]) {
 665//       edge_profile([TOP+RIGHT, BOT+FRONT], excess=10, convexity=20) {
 666//           mask2d_roundover(h=5,mask_angle=$edge_angle);
 667//       }
 668//   }
 669// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
 670//   prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
 671//       show_anchors();
 672
 673module prismoid(
 674    size1=undef, size2=undef, h, shift=[undef,undef],
 675    xang, yang,
 676    rounding=0, rounding1, rounding2,
 677    chamfer=0, chamfer1, chamfer2,
 678    l, height, length, center,
 679    anchor, spin=0, orient=UP
 680)
 681{
 682    vnf_s1_s2_shift = prismoid(
 683        size1=size1, size2=size2, h=h, shift=shift,
 684        xang=xang, yang=yang, 
 685        rounding=rounding, chamfer=chamfer, 
 686        rounding1=rounding1, rounding2=rounding2,
 687        chamfer1=chamfer1, chamfer2=chamfer2,
 688        l=l, height=height, length=length, anchor=BOT, _return_dim=true
 689    );
 690    anchor = get_anchor(anchor, center, BOT, BOT);
 691    attachable(anchor,spin,orient, size=vnf_s1_s2_shift[1], size2=vnf_s1_s2_shift[2], shift=vnf_s1_s2_shift[3]) {
 692        down(vnf_s1_s2_shift[1].z/2)
 693            vnf_polyhedron(vnf_s1_s2_shift[0], convexity=4);
 694        children();
 695    }
 696}
 697
 698function prismoid(
 699    size1, size2, h, shift=[0,0],
 700    rounding=0, rounding1, rounding2,
 701    chamfer=0, chamfer1, chamfer2,
 702    l, height, length, center,
 703    anchor=DOWN, spin=0, orient=UP, xang, yang,
 704    _return_dim=false
 705    
 706) =
 707    assert(is_undef(shift) || is_num(shift) || len(shift)==2, "shift must be a number or list of length 2")
 708    assert(is_undef(size1) || is_num(size1) || len(size1)==2, "size1 must be a number or list of length 2")
 709    assert(is_undef(size2) || is_num(size2) || len(size2)==2, "size2 must be a number or list of length 2")  
 710    let(
 711        xang = force_list(xang,2),
 712        yang = force_list(yang,2),
 713        yangOK = len(yang)==2 && (yang==[undef,undef] || (all_positive(yang) && yang[0]<180 && yang[1]<180)),
 714        xangOK = len(xang)==2 && (xang==[undef,undef] || (all_positive(xang) && xang[0]<180 && xang[1]<180)),
 715        size1=force_list(size1,2),
 716        size2=force_list(size2,2),
 717        h=first_defined([l,h,length,height]),
 718        shift = force_list(shift,2)
 719    )
 720    assert(xangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 721    assert(yangOK, "prismoid angles must be scalar or 2-vector, strictly between 0 and 180")
 722    assert(xang==[undef,undef] || shift.x==undef, "Cannot specify xang and a shift.x value together")
 723    assert(yang==[undef,undef] || shift.y==undef, "Cannot specify yang and a shift.y value together")
 724    assert(all_positive([h]) || is_undef(h), "h must be a positive value")
 725    let(
 726        hx = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang)[0],
 727        hy = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)[0]
 728    )
 729    assert(num_defined([hx,hy])>0, "Height not given and specification does not determine prismoid height")
 730    assert(hx==undef || hy==undef || approx(hx,hy),
 731           str("X and Y angle specifications give rise to conflicting height values ",hx," and ",hy))
 732    let(
 733        h = first_defined([hx,hy]),
 734        x_h_w1_w2_shift = _trapezoid_dims(h,size1.x,size2.x,shift.x,xang),
 735        y_h_w1_w2_shift = _trapezoid_dims(h,size1.y,size2.y,shift.y,yang)
 736    )
 737    let(
 738        s1 = [x_h_w1_w2_shift[1], y_h_w1_w2_shift[1]],
 739        s2 = [x_h_w1_w2_shift[2], y_h_w1_w2_shift[2]],
 740        shift = [x_h_w1_w2_shift[3], y_h_w1_w2_shift[3]]
 741    )
 742    assert(is_vector(s1,2), "Insufficient information to define prismoid")
 743    assert(is_vector(s2,2), "Insufficient information to define prismoid")
 744    assert(all_nonnegative(concat(s1,s2)),"Degenerate prismoid geometry")
 745    assert(s1.x+s2.x>0 && s1.y+s2.y>0, "Degenerate prismoid geometry")
 746    assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
 747    assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
 748    assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
 749    assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
 750    assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
 751    assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
 752    let(
 753        chamfer1=force_list(default(chamfer1,chamfer),4),
 754        chamfer2=force_list(default(chamfer2,chamfer),4),
 755        rounding1=force_list(default(rounding1,rounding),4),
 756        rounding2=force_list(default(rounding2,rounding),4)
 757    )
 758    assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
 759    assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
 760    assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
 761    assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
 762    assert(all_zero(v_mul(rounding1,chamfer1),0),
 763           "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 764    assert(all_zero(v_mul(rounding2,chamfer2),0),
 765           "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
 766    let(
 767        rounding1 = default(rounding1, rounding),
 768        rounding2 = default(rounding2, rounding),
 769        chamfer1 = default(chamfer1, chamfer),
 770        chamfer2 = default(chamfer2, chamfer),
 771        anchor = get_anchor(anchor, center, BOT, BOT),
 772        path1 = rect(s1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
 773        path2 = rect(s2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
 774        points = [
 775                    each path3d(path1, -h/2),
 776                    each path3d(move(shift, path2), +h/2),
 777                 ],
 778        faces = hull(points),
 779        vnf = [points, faces]
 780    )
 781    _return_dim ? [reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf),point3d(s1,h),s2,shift]
 782                : reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
 783
 784
 785// Function&Module: octahedron()
 786// Synopsis: Creates an octahedron with axis-aligned points.
 787// SynTags: Geom, VNF
 788// Topics: Shapes (3D), Attachable, VNF Generators
 789// See Also: prismoid()
 790// Usage: As Module
 791//   octahedron(size, ...) [ATTACHMENTS];
 792// Usage: As Function
 793//   vnf = octahedron(size, ...);
 794// Description:
 795//   When called as a module, creates an octahedron with axis-aligned points.
 796//   When called as a function, creates a [VNF](vnf.scad) of an octahedron with axis-aligned points.
 797// Arguments:
 798//   size = Width of the octahedron, tip to tip.
 799//   ---
 800//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
 801//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 802//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 803// Example:
 804//   octahedron(size=40);
 805// Example: Anchors
 806//   octahedron(size=40) show_anchors();
 807
 808module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
 809    vnf = octahedron(size=size);
 810    attachable(anchor,spin,orient, vnf=vnf, extent=true) {
 811        vnf_polyhedron(vnf, convexity=2);
 812        children();
 813    }
 814}
 815
 816function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
 817    let(
 818        size = scalar_vec3(size),
 819        s = size/2,
 820        vnf = [
 821            [ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
 822            [ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
 823        ]
 824    ) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
 825
 826
 827// Module: rect_tube()
 828// Synopsis: Creates a rectangular tube.
 829// SynTags: Geom
 830// Topics: Shapes (3D), Attachable, VNF Generators
 831// See Also: tube()
 832// Usage: Typical Rectangular Tubes
 833//   rect_tube(h, size, isize, [center], [shift]);
 834//   rect_tube(h, size, wall=, [center=]);
 835//   rect_tube(h, isize=, wall=, [center=]);
 836// Usage: Tapering Rectangular Tubes
 837//   rect_tube(h, size1=, size2=, wall=, ...);
 838//   rect_tube(h, isize1=, isize2=, wall=, ...);
 839//   rect_tube(h, size1=, size2=, isize1=, isize2=, ...);
 840// Usage: Chamfered
 841//   rect_tube(h, size, isize, chamfer=, ...);
 842//   rect_tube(h, size, isize, chamfer1=, chamfer2= ...);
 843//   rect_tube(h, size, isize, ichamfer=, ...);
 844//   rect_tube(h, size, isize, ichamfer1=, ichamfer2= ...);
 845//   rect_tube(h, size, isize, chamfer=, ichamfer=, ...);
 846// Usage: Rounded
 847//   rect_tube(h, size, isize, rounding=, ...);
 848//   rect_tube(h, size, isize, rounding1=, rounding2= ...);
 849//   rect_tube(h, size, isize, irounding=, ...);
 850//   rect_tube(h, size, isize, irounding1=, irounding2= ...);
 851//   rect_tube(h, size, isize, rounding=, irounding=, ...);
 852// Usage: Attaching Children
 853//   rect_tube(...) ATTACHMENTS;
 854//
 855// Description:
 856//   Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
 857//   You can only round or chamfer the vertical(ish) edges.  For those edges, you can
 858//   specify rounding and/or chamferring per-edge, and for top and bottom, inside and
 859//   outside  separately.
 860//   .
 861//   By default if you specify a chamfer or rounding then it applies as specified to the
 862//   outside, and an inside rounding is calculated that will maintain constant width
 863//   if your wall thickness is uniform.  If the wall thickness is not uniform, the default
 864//   inside rounding is calculated based on the smaller of the two wall thicknesses.
 865//   Note that the values of the more specific chamfers and roundings inherit from the
 866//   more general ones, so `rounding2` is determined from `rounding`.  The constant
 867//   width default will apply when the inner rounding and chamfer are both undef.
 868//   You can give an inner chamfer or rounding as a list with undef entries if you want to specify
 869//   some corner roundings and allow others to be computed.  
 870// Arguments:
 871//   h/l/height/length = The height or length of the rectangular tube.  Default: 1
 872//   size = The outer [X,Y] size of the rectangular tube.
 873//   isize = The inner [X,Y] size of the rectangular tube.
 874//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
 875//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
 876//   ---
 877//   wall = The thickness of the rectangular tube wall.
 878//   size1 = The [X,Y] size of the outside of the bottom of the rectangular tube.
 879//   size2 = The [X,Y] size of the outside of the top of the rectangular tube.
 880//   isize1 = The [X,Y] size of the inside of the bottom of the rectangular tube.
 881//   isize2 = The [X,Y] size of the inside of the top of the rectangular tube.
 882//   rounding = The roundover radius for the outside edges of the rectangular tube.
 883//   rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
 884//   rounding2 = The roundover radius for the outside top corner of the rectangular tube.
 885//   chamfer = The chamfer size for the outside edges of the rectangular tube.
 886//   chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
 887//   chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
 888//   irounding = The roundover radius for the inside edges of the rectangular tube. Default: Computed for uniform wall thickness (see above)
 889//   irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
 890//   irounding2 = The roundover radius for the inside top corner of the rectangular tube.
 891//   ichamfer = The chamfer size for the inside edges of the rectangular tube.  Default: Computed for uniform wall thickness (see above)
 892//   ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
 893//   ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
 894//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
 895//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
 896//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
 897// Examples:
 898//   rect_tube(size=50, wall=5, h=30);
 899//   rect_tube(size=[100,60], wall=5, h=30);
 900//   rect_tube(isize=[60,80], wall=5, h=30);
 901//   rect_tube(size=[100,60], isize=[90,50], h=30);
 902//   rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
 903// Example:
 904//   rect_tube(
 905//       size1=[100,60], size2=[70,40],
 906//       isize1=[40,20], isize2=[65,35], h=15
 907//   );
 908// Example: With rounding
 909//   rect_tube(size=100, wall=5, rounding=10, h=30);
 910// Example: With rounding
 911//   rect_tube(size=100, wall=5, chamfer=10, h=30);
 912// Example: Outer Rounding Only
 913//   rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
 914// Example: Outer Chamfer Only
 915//   rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
 916// Example: Outer Rounding, Inner Chamfer
 917//   rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
 918// Example: Inner Rounding, Outer Chamfer
 919//   rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
 920// Example: Gradiant Rounding
 921//   rect_tube(
 922//       size1=100, size2=80, wall=5, h=30,
 923//       rounding1=10, rounding2=0,
 924//       irounding1=8, irounding2=0
 925//   );
 926// Example: Per Corner Rounding
 927//   rect_tube(
 928//       size=100, wall=10, h=30,
 929//       rounding=[0,5,10,15], irounding=0
 930//   );
 931// Example: Per Corner Chamfer
 932//   rect_tube(
 933//       size=100, wall=10, h=30,
 934//       chamfer=[0,5,10,15], ichamfer=0
 935//   );
 936// Example: Mixing Chamfer and Rounding
 937//   rect_tube(
 938//       size=100, wall=10, h=30,
 939//       chamfer=[0,10,0,20], 
 940//       rounding=[10,0,20,0]
 941//   );
 942// Example: Really Mixing It Up
 943//   rect_tube(
 944//       size1=[100,80], size2=[80,60],
 945//       isize1=[50,30], isize2=[70,50], h=20,
 946//       chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
 947//       chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
 948//       rounding1=[5,0,10,0], irounding1=[3,0,8,0],
 949//       rounding2=[0,5,0,10], irounding2=[0,3,0,8]
 950//   );
 951// Example: Some interiors chamfered, others with default rounding
 952//   rect_tube(
 953//       size=100, wall=10, h=30,
 954//       rounding=[0,10,20,30], ichamfer=[8,8,undef,undef]
 955//   );
 956
 957
 958
 959function _rect_tube_rounding(factor,ir,r,alternative,size,isize) =
 960    let(wall = min(size-isize)/2*factor)
 961    [for(i=[0:3])
 962      is_def(ir[i]) ? ir[i]
 963    : is_undef(alternative[i]) ? max(0,r[i]-wall)
 964    : 0
 965    ];
 966    
 967module rect_tube(
 968    h, size, isize, center, shift=[0,0],
 969    wall, size1, size2, isize1, isize2,
 970    rounding=0, rounding1, rounding2,
 971    irounding=undef, irounding1=undef, irounding2=undef,
 972    chamfer=0, chamfer1, chamfer2,
 973    ichamfer=undef, ichamfer1=undef, ichamfer2=undef,
 974    anchor, spin=0, orient=UP,
 975    l, length, height
 976) {
 977    h = one_defined([h,l,length,height],"h,l,length,height");
 978    checks =
 979        assert(is_num(h), "l or h argument required.")
 980        assert(is_vector(shift,2));
 981    s1 = is_num(size1)? [size1, size1] :
 982        is_vector(size1,2)? size1 :
 983        is_num(size)? [size, size] :
 984        is_vector(size,2)? size :
 985        undef;
 986    s2 = is_num(size2)? [size2, size2] :
 987        is_vector(size2,2)? size2 :
 988        is_num(size)? [size, size] :
 989        is_vector(size,2)? size :
 990        undef;
 991    is1 = is_num(isize1)? [isize1, isize1] :
 992        is_vector(isize1,2)? isize1 :
 993        is_num(isize)? [isize, isize] :
 994        is_vector(isize,2)? isize :
 995        undef;
 996    is2 = is_num(isize2)? [isize2, isize2] :
 997        is_vector(isize2,2)? isize2 :
 998        is_num(isize)? [isize, isize] :
 999        is_vector(isize,2)? isize :
1000        undef;
1001    size1 = is_def(s1)? s1 :
1002        (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
1003        undef;
1004    size2 = is_def(s2)? s2 :
1005        (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
1006        undef;
1007    isize1 = is_def(is1)? is1 :
1008        (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
1009        undef;
1010    isize2 = is_def(is2)? is2 :
1011        (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
1012        undef;
1013    checks2 =
1014        assert(wall==undef || is_num(wall))
1015        assert(size1!=undef, "Bad size/size1 argument.")
1016        assert(size2!=undef, "Bad size/size2 argument.")
1017        assert(isize1!=undef, "Bad isize/isize1 argument.")
1018        assert(isize2!=undef, "Bad isize/isize2 argument.")
1019        assert(isize1.x < size1.x, "Inner size is larger than outer size.")
1020        assert(isize1.y < size1.y, "Inner size is larger than outer size.")
1021        assert(isize2.x < size2.x, "Inner size is larger than outer size.")
1022        assert(isize2.y < size2.y, "Inner size is larger than outer size.")
1023        assert(is_num(rounding) || is_vector(rounding,4), "rounding must be a number or 4-vector")
1024        assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "rounding1 must be a number or 4-vector")
1025        assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "rounding2 must be a number or 4-vector")
1026        assert(is_num(chamfer) || is_vector(chamfer,4), "chamfer must be a number or 4-vector")
1027        assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "chamfer1 must be a number or 4-vector")
1028        assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "chamfer2 must be a number or 4-vector")
1029        assert(is_undef(irounding) || is_num(irounding) || (is_list(irounding) && len(irounding)==4), "irounding must be a number or 4-vector")
1030        assert(is_undef(irounding1) || is_num(irounding1) || (is_list(irounding1) && len(irounding1)==4), "irounding1 must be a number or 4-vector")
1031        assert(is_undef(irounding2) || is_num(irounding2) || (is_list(irounding2) && len(irounding2)==4), "irounding2 must be a number or 4-vector")      
1032        assert(is_undef(ichamfer) || is_num(ichamfer) || (is_list(ichamfer) && len(ichamfer)==4), "ichamfer must be a number or 4-vector")
1033        assert(is_undef(ichamfer1) || is_num(ichamfer1) || (is_list(ichamfer1) && len(ichamfer1)==4), "ichamfer1 must be a number or 4-vector")
1034        assert(is_undef(ichamfer2) || is_num(ichamfer2) || (is_list(ichamfer2) && len(ichamfer2)==4), "ichamfer2 must be a number or 4-vector");
1035    chamfer1=force_list(default(chamfer1,chamfer),4);
1036    chamfer2=force_list(default(chamfer2,chamfer),4);
1037    rounding1=force_list(default(rounding1,rounding),4);
1038    rounding2=force_list(default(rounding2,rounding),4);
1039    checks3 =
1040        assert(all_nonnegative(chamfer1), "chamfer/chamfer1 must be non-negative")
1041        assert(all_nonnegative(chamfer2), "chamfer/chamfer2 must be non-negative")
1042        assert(all_nonnegative(rounding1), "rounding/rounding1 must be non-negative")
1043        assert(all_nonnegative(rounding2), "rounding/rounding2 must be non-negative")        
1044        assert(all_zero(v_mul(rounding1,chamfer1),0), "rounding1 and chamfer1 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner")
1045        assert(all_zero(v_mul(rounding2,chamfer2),0), "rounding2 and chamfer2 (possibly inherited from rounding and chamfer) cannot both be nonzero at the same corner");
1046    irounding1_temp = force_list(default(irounding1,irounding),4);
1047    irounding2_temp = force_list(default(irounding2,irounding),4);    
1048    ichamfer1_temp = force_list(default(ichamfer1,ichamfer),4);
1049    ichamfer2_temp = force_list(default(ichamfer2,ichamfer),4);
1050    checksignr1 = [for(entry=irounding1_temp) if (is_def(entry) && entry<0) 1]==[];
1051    checksignr2 = [for(entry=irounding2_temp) if (is_def(entry) && entry<0) 1]==[];    
1052    checksignc1 = [for(entry=ichamfer1_temp) if (is_def(entry) && entry<0) 1]==[];
1053    checksignc2 = [for(entry=ichamfer2_temp) if (is_def(entry) && entry<0) 1]==[];
1054    checkconflict1 = [for(i=[0:3]) if (is_def(irounding1_temp[i]) && is_def(ichamfer1_temp[i]) && irounding1_temp[i]!=0 && ichamfer1_temp[i]!=0) 1]==[];
1055    checkconflict2 = [for(i=[0:3]) if (is_def(irounding2_temp[i]) && is_def(ichamfer2_temp[i]) && irounding2_temp[i]!=0 && ichamfer2_temp[i]!=0) 1]==[];
1056    checks4 =
1057        assert(checksignr1, "irounding/irounding1 must be non-negative")
1058        assert(checksignr2, "irounding/irounding2 must be non-negative")
1059        assert(checksignc1, "ichamfer/ichamfer1 must be non-negative")
1060        assert(checksignc2, "ichamfer/ichamfer2 must be non-negative")
1061        assert(checkconflict1, "irounding1 and ichamfer1 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner")
1062        assert(checkconflict2, "irounding2 and ichamfer2 (possibly inherited from irounding and ichamfer) cannot both be nonzero at the swame corner");
1063    irounding1 = _rect_tube_rounding(1,irounding1_temp, rounding1, ichamfer1_temp, size1, isize1);
1064    irounding2 = _rect_tube_rounding(1,irounding2_temp, rounding2, ichamfer2_temp, size2, isize2);
1065    ichamfer1 = _rect_tube_rounding(1/sqrt(2),ichamfer1_temp, chamfer1, irounding1_temp, size1, isize1);
1066    ichamfer2 = _rect_tube_rounding(1/sqrt(2),ichamfer2_temp, chamfer2, irounding2_temp, size2, isize2);
1067    anchor = get_anchor(anchor, center, BOT, BOT);
1068    attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
1069        down(h/2) {
1070            difference() {
1071                prismoid(
1072                    size1, size2, h=h, shift=shift,
1073                    rounding1=rounding1, rounding2=rounding2,
1074                    chamfer1=chamfer1, chamfer2=chamfer2,
1075                    anchor=BOT
1076                );
1077                down(0.01) prismoid(
1078                    isize1, isize2, h=h+0.02, shift=shift,
1079                    rounding1=irounding1, rounding2=irounding2,
1080                    chamfer1=ichamfer1, chamfer2=ichamfer2,
1081                    anchor=BOT
1082                );
1083            }
1084        }
1085        children();
1086    }
1087}
1088
1089function rect_tube(
1090    h, size, isize, center, shift=[0,0],
1091    wall, size1, size2, isize1, isize2,
1092    rounding=0, rounding1, rounding2,
1093    irounding, irounding1, irounding2,
1094    chamfer=0, chamfer1, chamfer2,
1095    ichamfer, ichamfer1, ichamfer2,
1096    anchor, spin=0, orient=UP,
1097    l, length, height
1098) = no_function("rect_tube");
1099
1100
1101// Function&Module: wedge()
1102// Synopsis: Creates a 3d triangular wedge.
1103// SynTags: Geom, VNF
1104// Topics: Shapes (3D), Attachable, VNF Generators
1105// See also: prismoid(), rounded_prism(), pie_slice()
1106// Usage: As Module
1107//   wedge(size, [center], ...) [ATTACHMENTS];
1108// Usage: As Function
1109//   vnf = wedge(size, [center], ...);
1110//
1111// Description:
1112//   When called as a module, creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1113//   When called as a function, creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1114//
1115// Arguments:
1116//   size = [width, thickness, height]
1117//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
1118//   ---
1119//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT+BOTTOM`
1120//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1121//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1122//
1123// Extra Anchors:
1124//   hypot = Center of angled wedge face, perpendicular to that face.
1125//   hypot_left = Left side of angled wedge face, bisecting the angle between the left side and angled faces.
1126//   hypot_right = Right side of angled wedge face, bisecting the angle between the right side and angled faces.
1127//
1128// Example: Centered
1129//   wedge([20, 40, 15], center=true);
1130// Example: *Non*-Centered
1131//   wedge([20, 40, 15]);
1132// Example: Standard Anchors
1133//   wedge([40, 80, 30], center=true)
1134//       show_anchors(custom=false);
1135//   color([0.5,0.5,0.5,0.1])
1136//       cube([40, 80, 30], center=true);
1137// Example: Named Anchors
1138//   wedge([40, 80, 30], center=true)
1139//       show_anchors(std=false);
1140
1141module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
1142{
1143    size = scalar_vec3(size);
1144    anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
1145    vnf = wedge(size, anchor="origin");
1146    anchors = [
1147        named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1148        named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1149        named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1150    ];
1151    attachable(anchor,spin,orient, size=size, anchors=anchors) {
1152        if (size.z > 0) {
1153            vnf_polyhedron(vnf);
1154        }
1155        children();
1156    }
1157}
1158
1159
1160function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
1161    let(
1162        size = scalar_vec3(size),
1163        anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
1164        pts = [
1165            [ 1,1,-1], [ 1,-1,-1], [ 1,-1,1],
1166            [-1,1,-1], [-1,-1,-1], [-1,-1,1],
1167        ],
1168        faces = [
1169            [0,1,2], [3,5,4], [0,3,1], [1,3,4],
1170            [1,4,2], [2,4,5], [2,5,3], [0,2,3],
1171        ],
1172        vnf = [scale(size/2,p=pts), faces],
1173        anchors = [
1174            named_anchor("hypot", CTR, unit([0,size.z,size.y],UP)),
1175            named_anchor("hypot_left", [-size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+LEFT)),
1176            named_anchor("hypot_right", [size.x/2,0,0], unit(unit([0,size.z,size.y],UP)+RIGHT)),
1177        ]
1178    )
1179    reorient(anchor,spin,orient, size=size, anchors=anchors, p=vnf);
1180
1181
1182// Section: Cylinders
1183
1184
1185// Function&Module: cylinder()
1186// Synopsis: Creates an attachable cylinder.
1187// SynTags: Geom, VNF, Ext
1188// Topics: Shapes (3D), Attachable, VNF Generators
1189// See Also: cyl()
1190// Usage: As Module (as in native OpenSCAD)
1191//   cylinder(h, r=/d=, [center=]);
1192//   cylinder(h, r1/d1=, r2/d2=, [center=]);
1193// Usage: With BOSL2 anchoring and attachment extensions
1194//   cylinder(h, r=/d=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1195//   cylinder(h, r1/d1=, r2/d2=, [center=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
1196// Usage: As Function (BOSL2 extension)
1197//   vnf = cylinder(h, r=/d=, ...);
1198//   vnf = cylinder(h, r1/d1=, r2/d2=, ...);
1199// Description:
1200//   Creates a 3D cylinder or conic object.
1201//   This modules extends the built-in `cylinder()` module by adding support for attachment and by adding a function version.   
1202//   When called as a function, returns a [VNF](vnf.scad) for a cylinder.  
1203// Arguments:
1204//   h = The height of the cylinder.
1205//   r1 = The bottom radius of the cylinder.  (Before orientation.)
1206//   r2 = The top radius of the cylinder.  (Before orientation.)
1207//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.  Default: false
1208//   ---
1209//   d1 = The bottom diameter of the cylinder.  (Before orientation.)
1210//   d2 = The top diameter of the cylinder.  (Before orientation.)
1211//   r = The radius of the cylinder.
1212//   d = The diameter of the cylinder.
1213//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1214//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1215//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1216// Example: By Radius
1217//   xdistribute(30) {
1218//       cylinder(h=40, r=10);
1219//       cylinder(h=40, r1=10, r2=5);
1220//   }
1221// Example: By Diameter
1222//   xdistribute(30) {
1223//       cylinder(h=40, d=25);
1224//       cylinder(h=40, d1=25, d2=10);
1225//   }
1226// Example(Med): Anchoring
1227//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
1228// Example(Med): Spin
1229//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
1230// Example(Med): Orient
1231//   cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
1232// Example(Big): Standard Connectors
1233//   xdistribute(40) {
1234//       cylinder(h=30, d=25) show_anchors();
1235//       cylinder(h=30, d1=25, d2=10) show_anchors();
1236//   }
1237
1238module cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP)
1239{
1240    anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
1241    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1242    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1243    h = default(h,1);
1244    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
1245        _cylinder(h=h, r1=r1, r2=r2, center=true);
1246        children();
1247    }
1248}
1249
1250function cylinder(h, r1, r2, center, r, d, d1, d2, anchor, spin=0, orient=UP) =
1251    let(
1252        anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
1253        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1254        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1255        l = default(h,1),
1256        sides = segs(max(r1,r2)),
1257        verts = [
1258            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
1259            for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
1260        ],
1261        faces = [
1262            [for (i=[0:1:sides-1]) sides-1-i],
1263            for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
1264            for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
1265            [for (i=[0:1:sides-1]) sides+i]
1266        ]
1267    ) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
1268
1269
1270
1271// Function&Module: cyl()
1272// Synopsis: Creates an attachable cylinder with roundovers and chamfering.
1273// SynTags: Geom, VNF
1274// Topics: Cylinders, Textures, Rounding, Chamfers
1275// See Also: texture(), rotate_sweep(), cylinder()
1276// Usage: Normal Cylinders
1277//   cyl(l|h|length|height, r, [center], [circum=], [realign=]) [ATTACHMENTS];
1278//   cyl(l|h|length|height, d=, ...) [ATTACHMENTS];
1279//   cyl(l|h|length|height, r1=, r2=, ...) [ATTACHMENTS];
1280//   cyl(l|h|length|height, d1=, d2=, ...) [ATTACHMENTS];
1281//
1282// Usage: Chamferred Cylinders
1283//   cyl(l|h|length|height, r|d, chamfer=, [chamfang=], [from_end=], ...);
1284//   cyl(l|h|length|height, r|d, chamfer1=, [chamfang1=], [from_end=], ...);
1285//   cyl(l|h|length|height, r|d, chamfer2=, [chamfang2=], [from_end=], ...);
1286//   cyl(l|h|length|height, r|d, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
1287//
1288// Usage: Rounded End Cylinders
1289//   cyl(l|h|length|height, r|d, rounding=, ...);
1290//   cyl(l|h|length|height, r|d, rounding1=, ...);
1291//   cyl(l|h|length|height, r|d, rounding2=, ...);
1292//   cyl(l|h|length|height, r|d, rounding1=, rounding2=, ...);
1293//
1294// Usage: Textured Cylinders
1295//   cyl(l|h|length|height, r|d, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1296//   cyl(l|h|length|height, r1=, r2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1297//   cyl(l|h|length|height, d1=, d2=, texture=, [tex_size=]|[tex_reps=], [tex_depth=], [tex_rot=], [tex_samples=], [style=], [tex_taper=], [tex_inset=], ...);
1298//
1299// Usage: Caled as a function to get a VNF
1300//   vnf = cyl(...);
1301//
1302// Description:
1303//   Creates cylinders in various anchorings and orientations, with optional rounding, chamfers, or textures.
1304//   You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
1305//   or `r1`|`d1` and `r2`|`d2`.  Note: the chamfers and rounding cannot be cumulatively longer than
1306//   the cylinder or cone's sloped side.  The more specific parameters like chamfer1 or rounding2 override the more
1307//   general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
1308//   rounding at the bottom.
1309// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): Chamfers on cones can be tricky.  This figure shows chamfers of the same size and same angle, A=30 degrees.  Note that the angle is measured on the inside, and produces a quite different looking chamfer at the top and bottom of the cone.  Straight black arrows mark the size of the chamfers, which may not even appear the same size visually.  When you do not give an angle, the triangle that is cut off will be isoceles, like the triangle at the top, with two equal angles.
1310//  color("lightgray")
1311//  projection()
1312//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=0,orient=BACK);
1313//  projection()
1314//      cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=8,orient=BACK);
1315//  color("black"){
1316//      fwd(9.6)right(20-4.8)text("A",size=1.3);
1317//      fwd(-8.4)right(10-4.9)text("A",size=1.3);
1318//      right(20-8)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1319//      right(10-8)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1320//      stroke(arc(cp=[2,10], angle=[0,-30], n=20, r=5), width=.18, endcaps="arrow2");
1321//      stroke(arc(cp=[12,-10], angle=[0,30], n=20, r=5), width=.18, endcaps="arrow2");
1322//  }
1323// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): The cone in this example is narrow but has the same slope.  With negative chamfers, the angle A=30 degrees is on the outside.  The chamfers are again quite different looking.  As before, the default will feature two congruent angles, and in this case it happens at the bottom of the cone but not the top.  The straight arrows again show the size of the chamfer.
1324//  r1=10-7.5;r2=20-7.5;
1325//  color("lightgray")
1326//  projection()
1327//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=-8,orient=BACK);
1328//  projection()
1329//      cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=0,orient=BACK);
1330//  color("black"){
1331//      fwd(9.7)right(r2+3.8)text("A",size=1.3);
1332//      fwd(-8.5)right(r1+3.7)text("A",size=1.3);
1333//      right(r2)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1334//      right(r1)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1335//      stroke(arc(cp=[r1+8,10], angle=[180,180+30], n=20, r=5), width=.18, endcaps="arrow2");
1336//      stroke(arc(cp=[r2+8,-10], angle=[180-30,180], n=20, r=5), width=.18, endcaps="arrow2");
1337//  }
1338// Arguments:
1339//   l / h / length / height = Length of cylinder along oriented axis.  Default: 1
1340//   r = Radius of cylinder.  Default: 1
1341//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1342//   ---
1343//   r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
1344//   r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
1345//   d = Diameter of cylinder.
1346//   d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
1347//   d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
1348//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1349//   shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
1350//   chamfer = The size of the chamfers on the ends of the cylinder.  (Also see: `from_end=`)  Default: none.
1351//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  (Also see: `from_end1=`)  Default: none.
1352//   chamfer2 = The size of the chamfer on the top end of the cylinder.  (Also see: `from_end2=`)  Default: none.
1353//   chamfang = The angle in degrees of the chamfers away from the ends of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1354//   chamfang1 = The angle in degrees of the bottom chamfer away from the bottom end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1355//   chamfang2 = The angle in degrees of the top chamfer away from the top end of the cylinder.  Default: Chamfer angle is halfway between the endcap and cone face.
1356//   from_end = If true, chamfer is measured along the conic face from the ends of the cylinder, instead of inset from the edge.  Default: `false`.
1357//   from_end1 = If true, chamfer on the bottom end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1358//   from_end2 = If true, chamfer on the top end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1359//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1360//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1361//   rounding2 = The radius of the rounding on the top end of the cylinder.
1362//   realign = If true, rotate the cylinder by half the angle of one face.
1363//   teardrop = If given as a number, rounding around the bottom edge of the cylinder won't exceed this many degrees from vertical.  If true, the limit angle is 45 degrees.  Default: `false`
1364//   texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces.  See {{texture()}} for what named textures are supported.
1365//   tex_size = An optional 2D target size for the textures.  Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
1366//   tex_reps = If given instead of tex_size, a 2-vector giving the number of texture tile repetitions in the horizontal and vertical directions.
1367//   tex_inset = If numeric, lowers the texture into the surface by the specified proportion, e.g. 0.5 would lower it half way into the surface.  If `true`, insets by exactly its full depth.  Default: `false`
1368//   tex_rot = Rotate texture by specified angle, which must be a multiple of 90 degrees.  Default: 0
1369//   tex_depth = Specify texture depth; if negative, invert the texture.  Default: 1.  
1370//   tex_samples = Minimum number of "bend points" to have in VNF texture tiles.  Default: 8
1371//   tex_taper = If given as a number, tapers the texture height to zero over the first and last given percentage of the path.  If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights.  Default: `undef` (no taper)
1372//   style = {{vnf_vertex_array()}} style used to triangulate heightfield textures.  Default: "min_edge"
1373//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1374//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1375//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1376//
1377//
1378// Example: By Radius
1379//   xdistribute(30) {
1380//       cyl(l=40, r=10);
1381//       cyl(l=40, r1=10, r2=5);
1382//   }
1383//
1384// Example: By Diameter
1385//   xdistribute(30) {
1386//       cyl(l=40, d=25);
1387//       cyl(l=40, d1=25, d2=10);
1388//   }
1389//
1390// Example: Chamferring
1391//   xdistribute(60) {
1392//       // Shown Left to right.
1393//       cyl(l=40, d=40, chamfer=7);  // Default chamfang=45
1394//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
1395//       cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
1396//   }
1397//
1398// Example: Rounding
1399//   cyl(l=40, d=40, rounding=10);
1400//
1401// Example(VPD=175;VPR=[90,0,0]): Teardrop Bottom Rounding
1402//   cyl(l=40, d=40, rounding=10, teardrop=true);
1403//
1404// Example: Heterogenous Chamfers and Rounding
1405//   ydistribute(80) {
1406//       // Shown Front to Back.
1407//       cyl(l=40, d=40, rounding1=15, orient=UP);
1408//       cyl(l=40, d=40, chamfer2=5, orient=UP);
1409//       cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
1410//   }
1411//
1412// Example: Putting it all together
1413//   cyl(
1414//       l=20, d1=25, d2=15,
1415//       chamfer1=5, chamfang1=60,
1416//       from_end=true, rounding2=5
1417//   );
1418//
1419// Example: External Chamfers
1420//   cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
1421//
1422// Example: External Roundings
1423//   cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
1424//
1425// Example(Med): Standard Connectors
1426//   xdistribute(40) {
1427//       cyl(l=30, d=25) show_anchors();
1428//       cyl(l=30, d1=25, d2=10) show_anchors();
1429//   }
1430//
1431// Example: Texturing with heightfield diamonds
1432//   cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]);
1433//
1434// Example: Texturing with heightfield pyramids
1435//   cyl(h=40, r1=20, r2=15,
1436//       texture="pyramids", tex_size=[5,5],
1437//       style="convex");
1438//
1439// Example: Texturing with heightfield truncated pyramids
1440//   cyl(h=40, r1=20, r2=15, chamfer=5,
1441//       texture="trunc_pyramids",
1442//       tex_size=[5,5], style="convex");
1443//
1444// Example: Texturing with VNF tile "dots"
1445//   cyl(h=40, r1=20, r2=15, rounding=9,
1446//       texture="dots", tex_size=[5,5],
1447//       tex_samples=6);
1448//
1449// Example: Texturing with VNF tile "bricks_vnf"
1450//   cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10,
1451//       texture="bricks_vnf", tex_size=[10,10],
1452//       tex_depth=0.5, style="concave");
1453//
1454// Example: No Texture Taper
1455//   cyl(d1=25, d2=20, h=30, rounding=5,
1456//       texture="trunc_ribs", tex_size=[5,1]);
1457//
1458// Example: Taper Texure at Extreme Ends
1459//   cyl(d1=25, d2=20, h=30, rounding=5,
1460//       texture="trunc_ribs", tex_taper=0,
1461//       tex_size=[5,1]);
1462//
1463// Example: Taper Texture over First and Last 10%
1464//   cyl(d1=25, d2=20, h=30, rounding=5,
1465//       texture="trunc_ribs", tex_taper=10,
1466//       tex_size=[5,1]);
1467//
1468// Example: Making a Clay Pattern Roller
1469//   tex = [
1470//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1471//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1472//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1473//       [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1474//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1475//       [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1476//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1477//       [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1478//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1479//       [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1480//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1481//       [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1482//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1483//       [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1484//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1485//       [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1486//       [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1487//   ];
1488//   diff()
1489//   cyl(d=20*10/PI, h=10, chamfer=0,
1490//       texture=tex, tex_reps=[20,1], tex_depth=-1,
1491//       tex_taper=undef, style="concave") {
1492//           attach([TOP,BOT]) {
1493//               cyl(d1=20*10/PI, d2=30, h=5, anchor=BOT)
1494//                   attach(TOP) {
1495//                       tag("remove") zscale(0.5) up(3) sphere(d=15);
1496//                   }
1497//           }
1498//   }
1499
1500function cyl(
1501    h, r, center,
1502    l, r1, r2,
1503    d, d1, d2,
1504    length, height,
1505    chamfer, chamfer1, chamfer2,
1506    chamfang, chamfang1, chamfang2,
1507    rounding, rounding1, rounding2,
1508    circum=false, realign=false, shift=[0,0],
1509    teardrop=false,
1510    from_end, from_end1, from_end2,
1511    texture, tex_size=[5,5], tex_reps, tex_counts,
1512    tex_inset=false, tex_rot=0,
1513    tex_scale, tex_depth, tex_samples, length, height, 
1514    tex_taper, style, tex_style,
1515    anchor, spin=0, orient=UP
1516) =
1517    assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'.  You cannot give both.")
1518    assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'.  You cannot give both.")    
1519    assert(num_defined([tex_scale,tex_depth])<2, "In linear_sweep() the 'tex_scale' parameter has been replaced by 'tex_depth'.  You cannot give both.")
1520    let(
1521        style = is_def(tex_style)? echo("In cyl() the 'tex_style' parameter is deprecated and has been replaced by 'style'")tex_style
1522              : default(style,"min_edge"),
1523        tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
1524                 : tex_reps,
1525        tex_depth = is_def(tex_scale)? echo("In rotate_sweep() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
1526                  : default(tex_depth,1),
1527        l = one_defined([l, h, length, height],"l,h,length,height",dflt=1),
1528        _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1529        _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1530        sides = segs(max(_r1,_r2)),
1531        sc = circum? 1/cos(180/sides) : 1,
1532        r1 = _r1 * sc,
1533        r2 = _r2 * sc,
1534        phi = atan2(l, r2-r1),
1535        anchor = get_anchor(anchor,center,BOT,CENTER)
1536    )
1537    assert(is_finite(l), "l/h/length/height must be a finite number.")
1538    assert(is_finite(r1), "r/r1/d/d1 must be a finite number.")
1539    assert(is_finite(r2), "r2 or d2 must be a finite number.")
1540    assert(is_vector(shift,2), "shift must be a 2D vector.")
1541    let(
1542        vnf = !any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])
1543          ? cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides)
1544          : let(
1545                vang = atan2(r1-r2,l),
1546                _chamf1 = first_defined([chamfer1, if (is_undef(rounding1)) chamfer, 0]),
1547                _chamf2 = first_defined([chamfer2, if (is_undef(rounding2)) chamfer, 0]),
1548                _fromend1 = first_defined([from_end1, from_end, false]),
1549                _fromend2 = first_defined([from_end2, from_end, false]),
1550                chang1 = first_defined([chamfang1, chamfang, 45+sign(_chamf1)*vang/2]),
1551                chang2 = first_defined([chamfang2, chamfang, 45-sign(_chamf2)*vang/2]),
1552                round1 = first_defined([rounding1, if (is_undef(chamfer1)) rounding, 0]),
1553                round2 = first_defined([rounding2, if (is_undef(chamfer2)) rounding, 0]),
1554                checks1 =
1555                    assert(is_finite(_chamf1), "chamfer1 must be a finite number if given.")
1556                    assert(is_finite(_chamf2), "chamfer2 must be a finite number if given.")
1557                    assert(is_finite(chang1) && chang1>0, "chamfang1 must be a positive number if given.")
1558                    assert(is_finite(chang2) && chang2>0, "chamfang2 must be a positive number if given.")
1559                    assert(chang1<90+sign(_chamf1)*vang, "chamfang1 must be smaller than the cone face angle")
1560                    assert(chang2<90-sign(_chamf2)*vang, "chamfang2 must be smaller than the cone face angle")
1561                    assert(num_defined([chamfer1,rounding1])<2, "cannot define both chamfer1 and rounding1")
1562                    assert(num_defined([chamfer2,rounding2])<2, "cannot define both chamfer2 and rounding2")
1563                    assert(num_defined([chamfer,rounding])<2, "cannot define both chamfer and rounding")                                
1564                    undef,
1565                chamf1r = !_chamf1? 0
1566                        : !_fromend1? _chamf1
1567                        : law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
1568                chamf2r = !_chamf2? 0
1569                        : !_fromend2? _chamf2
1570                        : law_of_sines(a=_chamf2, A=chang2, B=180-chang2-(90+sign(_chamf2)*vang)),
1571                chamf1l = !_chamf1? 0
1572                        : _fromend1? abs(_chamf1)
1573                        : abs(law_of_sines(a=_chamf1, A=180-chang1-(90-sign(_chamf1)*vang), B=chang1)),
1574                chamf2l = !_chamf2? 0
1575                        : _fromend2? abs(_chamf2)
1576                        : abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
1577                facelen = adj_ang_to_hyp(l, abs(vang)),
1578
1579                cp1 = [r1,-l/2],
1580                cp2 = [r2,+l/2],
1581                roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
1582                                        : round1/tan(45+vang/2),
1583                roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
1584                                       : round2/tan(45-vang/2),
1585                dy1 = abs(_chamf1 ? chamf1l : round1 ? roundlen1 : 0), 
1586                dy2 = abs(_chamf2 ? chamf2l : round2 ? roundlen2 : 0),
1587
1588                td_ang = teardrop == true? 45 :
1589                    teardrop == false? 90 :
1590                    assert(is_finite(teardrop))
1591                    assert(teardrop>=0 && teardrop<=90)
1592                    teardrop,
1593
1594                checks2 =
1595                    assert(is_finite(round1), "rounding1 must be a number if given.")
1596                    assert(is_finite(round2), "rounding2 must be a number if given.")
1597                    assert(chamf1r <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
1598                    assert(chamf2r <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
1599                    assert(roundlen1 <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
1600                    assert(roundlen2 <= r2, "size of rounding2 is larger than the r2 radius of the cylinder.")
1601                    assert(dy1+dy2 <= facelen, "Chamfers/roundings don't fit on the cylinder/cone.  They exceed the length of the cylinder/cone face.")
1602                    undef,
1603                path = [
1604                    if (texture==undef) [0,-l/2],
1605
1606                    if (!approx(chamf1r,0))
1607                        each [
1608                            [r1, -l/2] + polar_to_xy(chamf1r,180),
1609                            [r1, -l/2] + polar_to_xy(chamf1l,90+vang),
1610                        ]
1611                    else if (!approx(round1,0) && td_ang < 90)
1612                        each _teardrop_corner(r=round1, corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]], ang=td_ang)
1613                    else if (!approx(round1,0) && td_ang >= 90)
1614                        each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]])
1615                    else [r1,-l/2],
1616
1617                    if (is_finite(chamf2r) && !approx(chamf2r,0))
1618                        each [
1619                            [r2, l/2] + polar_to_xy(chamf2l,270+vang),
1620                            [r2, l/2] + polar_to_xy(chamf2r,180),
1621                        ]
1622                    else if (is_finite(round2) && !approx(round2,0))
1623                        each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[max(0,r2-2*roundlen2),l/2]])
1624                    else [r2,l/2],
1625
1626                    if (texture==undef) [0,l/2],
1627                ]
1628            ) rotate_sweep(path,
1629                texture=texture, tex_reps=tex_reps, tex_size=tex_size,
1630                tex_inset=tex_inset, tex_rot=tex_rot,
1631                tex_depth=tex_depth, tex_samples=tex_samples,
1632                tex_taper=tex_taper, style=style, closed=false,
1633                _tex_inhibit_y_slicing=true
1634            ),
1635        skmat = down(l/2) *
1636            skew(sxz=shift.x/l, syz=shift.y/l) *
1637            up(l/2) *
1638            zrot(realign? 180/sides : 0),
1639        ovnf = apply(skmat, vnf)
1640    )
1641    reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf);
1642
1643
1644function _teardrop_corner(r, corner, ang=45) =
1645    let(
1646        check = assert(len(corner)==3)
1647            assert(is_finite(r))
1648            assert(is_finite(ang)),
1649        cp = circle_2tangents(abs(r), corner)[0],
1650        path1 = arc(r=abs(r), corner=corner),
1651        path2 = [
1652            for (p = select(path1,0,-2))
1653                if (abs(modang(v_theta(p-cp)-90)) <= 180-ang) p,
1654            last(path1)
1655        ],
1656        path = [
1657            line_intersection([corner[0],corner[1]],[path2[0],path2[0]+polar_to_xy(1,-90-ang*sign(r))]),
1658            each path2
1659        ]
1660    ) path;
1661
1662
1663module cyl(
1664    h, r, center,
1665    l, r1, r2,
1666    d, d1, d2,
1667    chamfer, chamfer1, chamfer2,
1668    chamfang, chamfang1, chamfang2,
1669    rounding, rounding1, rounding2,
1670    circum=false, realign=false, shift=[0,0],
1671    teardrop=false,
1672    from_end, from_end1, from_end2,
1673    texture, tex_size=[5,5], tex_reps, tex_counts,
1674    tex_inset=false, tex_rot=0,
1675    tex_scale, tex_depth, tex_samples, length, height, 
1676    tex_taper, style, tex_style,
1677    anchor, spin=0, orient=UP
1678) {
1679    dummy=
1680      assert(num_defined([style,tex_style])<2, "In cyl() the 'tex_style' parameters has been replaced by 'style'.  You cannot give both.")
1681      assert(num_defined([tex_reps,tex_counts])<2, "In cyl() the 'tex_counts' parameters has been replaced by 'tex_reps'.  You cannot give both.")
1682      assert(num_defined([tex_scale,tex_depth])<2, "In cyl() the 'tex_scale' parameter has been replaced by 'tex_depth'.  You cannot give both.");
1683    style = is_def(tex_style)? echo("In cyl the 'tex_style()' parameters is deprecated and has been replaced by 'style'")tex_style
1684          : default(style,"min_edge");
1685    tex_reps = is_def(tex_counts)? echo("In cyl() the 'tex_counts' parameter is deprecated and has been replaced by 'tex_reps'")tex_counts
1686             : tex_reps;
1687    tex_depth = is_def(tex_scale)? echo("In rotate_sweep() the 'tex_scale' parameter is deprecated and has been replaced by 'tex_depth'")tex_scale
1688              : default(tex_depth,1);
1689    l = one_defined([l, h, length, height],"l,h,length,height",dflt=1);
1690    _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1691    _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1692    sides = segs(max(_r1,_r2));
1693    sc = circum? 1/cos(180/sides) : 1;
1694    r1 = _r1 * sc;
1695    r2 = _r2 * sc;
1696    phi = atan2(l, r2-r1);
1697    anchor = get_anchor(anchor,center,BOT,CENTER);
1698    skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2);
1699    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) {
1700        multmatrix(skmat)
1701        zrot(realign? 180/sides : 0) {
1702            if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])) {
1703                cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
1704            } else {
1705                vnf = cyl(
1706                    l=l, r1=r1, r2=r2, center=true, 
1707                    chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1708                    chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1709                    rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1710                    from_end=from_end, from_end1=from_end1, from_end2=from_end2,
1711                    teardrop=teardrop,
1712                    texture=texture, tex_size=tex_size,
1713                    tex_reps=tex_reps, tex_depth=tex_depth,
1714                    tex_inset=tex_inset, tex_rot=tex_rot,
1715                    style=style, tex_taper=tex_taper,
1716                    tex_samples=tex_samples
1717                );
1718                vnf_polyhedron(vnf, convexity=texture!=undef? 2 : 10);
1719            }
1720        }
1721        children();
1722    }
1723}
1724
1725
1726
1727// Module: xcyl()
1728// Synopsis: creates a cylinder oriented along the X axis.
1729// SynTags: Geom
1730// Topics: Cylinders, Textures, Rounding, Chamfers
1731// See Also: texture(), rotate_sweep(), cyl()
1732// Description:
1733//   Creates an attachable cylinder with roundovers and chamfering oriented along the X axis.
1734//
1735// Usage: Typical
1736//   xcyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1737//   xcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1738//
1739// Arguments:
1740//   l / h / length / height = Length of cylinder along oriented axis. Default: 1
1741//   r = Radius of cylinder.  Default: 1
1742//   ---
1743//   r1 = Optional radius of left (X-) end of cylinder.
1744//   r2 = Optional radius of right (X+) end of cylinder.
1745//   d = Optional diameter of cylinder. (use instead of `r`)
1746//   d1 = Optional diameter of left (X-) end of cylinder.
1747//   d2 = Optional diameter of right (X+) end of cylinder.
1748//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1749//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1750//   chamfer1 = The size of the chamfer on the left end of the cylinder.  Default: none.
1751//   chamfer2 = The size of the chamfer on the right end of the cylinder.  Default: none.
1752//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1753//   chamfang1 = The angle in degrees of the chamfer on the left end of the cylinder.
1754//   chamfang2 = The angle in degrees of the chamfer on the right end of the cylinder.
1755//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1756//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1757//   rounding1 = The radius of the rounding on the left end of the cylinder.
1758//   rounding2 = The radius of the rounding on the right end of the cylinder.
1759//   realign = If true, rotate the cylinder by half the angle of one face.
1760//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1761//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1762//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1763//
1764// Example: By Radius
1765//   ydistribute(50) {
1766//       xcyl(l=35, r=10);
1767//       xcyl(l=35, r1=15, r2=5);
1768//   }
1769//
1770// Example: By Diameter
1771//   ydistribute(50) {
1772//       xcyl(l=35, d=20);
1773//       xcyl(l=35, d1=30, d2=10);
1774//   }
1775
1776function xcyl(
1777    h, r, d, r1, r2, d1, d2, l, 
1778    chamfer, chamfer1, chamfer2,
1779    chamfang, chamfang1, chamfang2,
1780    rounding, rounding1, rounding2,
1781    circum=false, realign=false, from_end=false, length, height,
1782    anchor=CENTER, spin=0, orient=UP
1783) = no_function("xcyl");
1784
1785module xcyl(
1786    h, r, d, r1, r2, d1, d2, l, 
1787    chamfer, chamfer1, chamfer2,
1788    chamfang, chamfang1, chamfang2,
1789    rounding, rounding1, rounding2,
1790    circum=false, realign=false, from_end=false, length, height,
1791    anchor=CENTER, spin=0, orient=UP
1792) {
1793    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1794    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1795    l = one_defined([l,h,length,height],"l,h,length,height",1);
1796    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=RIGHT) {
1797        cyl(
1798            l=l, r1=r1, r2=r2,
1799            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1800            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1801            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1802            circum=circum, realign=realign, from_end=from_end,
1803            anchor=CENTER, orient=RIGHT
1804        );
1805        children();
1806    }
1807}
1808
1809
1810// Module: ycyl()
1811// Synopsis: Creates a cylinder oriented along the y axis.
1812// SynTags: Geom
1813// Topics: Cylinders, Textures, Rounding, Chamfers
1814// See Also: texture(), rotate_sweep(), cyl()
1815// Description:
1816//   Creates an attachable cylinder with roundovers and chamfering oriented along the y axis.
1817//
1818// Usage: Typical
1819//   ycyl(l|h|length|height, r|d=, [anchor=], ...) [ATTACHMENTS];
1820//   ycyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1821//
1822// Arguments:
1823//   l / h / length / height = Length of cylinder along oriented axis. (Default: `1.0`)
1824//   r = Radius of cylinder.
1825//   ---
1826//   r1 = Radius of front (Y-) end of cone.
1827//   r2 = Radius of back (Y+) end of one.
1828//   d = Diameter of cylinder.
1829//   d1 = Diameter of front (Y-) end of one.
1830//   d2 = Diameter of back (Y+) end of one.
1831//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1832//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1833//   chamfer1 = The size of the chamfer on the front end of the cylinder.  Default: none.
1834//   chamfer2 = The size of the chamfer on the back end of the cylinder.  Default: none.
1835//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1836//   chamfang1 = The angle in degrees of the chamfer on the front end of the cylinder.
1837//   chamfang2 = The angle in degrees of the chamfer on the back end of the cylinder.
1838//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1839//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1840//   rounding1 = The radius of the rounding on the front end of the cylinder.
1841//   rounding2 = The radius of the rounding on the back end of the cylinder.
1842//   realign = If true, rotate the cylinder by half the angle of one face.
1843//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1844//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1845//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1846//
1847// Example: By Radius
1848//   xdistribute(50) {
1849//       ycyl(l=35, r=10);
1850//       ycyl(l=35, r1=15, r2=5);
1851//   }
1852//
1853// Example: By Diameter
1854//   xdistribute(50) {
1855//       ycyl(l=35, d=20);
1856//       ycyl(l=35, d1=30, d2=10);
1857//   }
1858
1859function ycyl(
1860    h, r, d, r1, r2, d1, d2, l,
1861    chamfer, chamfer1, chamfer2,
1862    chamfang, chamfang1, chamfang2,
1863    rounding, rounding1, rounding2,
1864    circum=false, realign=false, from_end=false,height,length,
1865    anchor=CENTER, spin=0, orient=UP
1866) = no_function("ycyl");
1867
1868
1869module ycyl(
1870    h, r, d, r1, r2, d1, d2, l,
1871    chamfer, chamfer1, chamfer2,
1872    chamfang, chamfang1, chamfang2,
1873    rounding, rounding1, rounding2,
1874    circum=false, realign=false, from_end=false,height,length,
1875    anchor=CENTER, spin=0, orient=UP
1876) {
1877    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1878    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1879    l = one_defined([l,h,length,height],"l,h,length,height",1);
1880    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1881        cyl(
1882            l=l, r1=r1, r2=r2,
1883            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1884            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1885            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1886            circum=circum, realign=realign, from_end=from_end,
1887            anchor=CENTER, orient=BACK
1888        );
1889        children();
1890    }
1891}
1892
1893
1894
1895// Module: zcyl()
1896// Synopsis: Creates a cylinder oriented along the Z axis.
1897// SynTags: Geom
1898// Topics: Cylinders, Textures, Rounding, Chamfers
1899// See Also: texture(), rotate_sweep(), cyl()
1900// Description:
1901//   Creates an attachable cylinder with roundovers and chamfering oriented along the Z axis.
1902//
1903// Usage: Typical
1904//   zcyl(l|h|length|height, r|d=, [anchor=],...) [ATTACHMENTS];
1905//   zcyl(l|h|length|height, r1=|d1=, r2=|d2=, [anchor=],...);
1906//
1907// Arguments:
1908//   l / h / length / height = Length of cylinder along oriented axis. (Default: 1.0)
1909//   r = Radius of cylinder.
1910//   ---
1911//   r1 = Radius of front (Y-) end of cone.
1912//   r2 = Radius of back (Y+) end of one.
1913//   d = Diameter of cylinder.
1914//   d1 = Diameter of front (Y-) end of one.
1915//   d2 = Diameter of back (Y+) end of one.
1916//   circum = If true, cylinder should circumscribe the circle of the given size.  Otherwise inscribes.  Default: `false`
1917//   chamfer = The size of the chamfers on the ends of the cylinder.  Default: none.
1918//   chamfer1 = The size of the chamfer on the bottom end of the cylinder.  Default: none.
1919//   chamfer2 = The size of the chamfer on the top end of the cylinder.  Default: none.
1920//   chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1921//   chamfang1 = The angle in degrees of the chamfer on the bottom end of the cylinder.
1922//   chamfang2 = The angle in degrees of the chamfer on the top end of the cylinder.
1923//   from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge.  Default: `false`.
1924//   rounding = The radius of the rounding on the ends of the cylinder.  Default: none.
1925//   rounding1 = The radius of the rounding on the bottom end of the cylinder.
1926//   rounding2 = The radius of the rounding on the top end of the cylinder.
1927//   realign = If true, rotate the cylinder by half the angle of one face.
1928//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1929//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1930//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1931//
1932// Example: By Radius
1933//   xdistribute(50) {
1934//       zcyl(l=35, r=10);
1935//       zcyl(l=35, r1=15, r2=5);
1936//   }
1937//
1938// Example: By Diameter
1939//   xdistribute(50) {
1940//       zcyl(l=35, d=20);
1941//       zcyl(l=35, d1=30, d2=10);
1942//   }
1943
1944function zcyl(
1945    h, r, d, r1, r2, d1, d2, l,
1946    chamfer, chamfer1, chamfer2,
1947    chamfang, chamfang1, chamfang2,
1948    rounding, rounding1, rounding2,
1949    circum=false, realign=false, from_end=false, length, height,
1950    anchor=CENTER, spin=0, orient=UP
1951) = no_function("zcyl");
1952
1953module zcyl(
1954    h, r, d, r1, r2, d1, d2, l,
1955    chamfer, chamfer1, chamfer2,
1956    chamfang, chamfang1, chamfang2,
1957    rounding, rounding1, rounding2,
1958    circum=false, realign=false, from_end=false, length, height,
1959    anchor=CENTER, spin=0, orient=UP
1960) {
1961    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1962    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1963    l = one_defined([l,h,length,height],"l,h,length,height",1);
1964    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1965        cyl(
1966            l=l, r1=r1, r2=r2,
1967            chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1968            chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1969            rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1970            circum=circum, realign=realign, from_end=from_end,
1971            anchor=CENTER
1972        );
1973        children();
1974    }
1975}
1976
1977
1978
1979// Module: tube()
1980// Synopsis: Creates a cylindrical or conical tube.
1981// SynTags: Geom
1982// Topics: Shapes (3D), Attachable, VNF Generators
1983// See Also: rect_tube()
1984// Description:
1985//   Makes a hollow tube that can be cylindrical or conical by specifying inner and outer dimensions or by giving one dimension and
1986//   wall thickness. 
1987// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
1988//   tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
1989//   tube(h|l, od=, id=, ...)  [ATTACHMENTS];
1990// Usage: Specify wall thickness
1991//   tube(h|l, or|od=|ir=|id=, wall=, ...) [ATTACHMENTS];
1992// Usage: Conical tubes
1993//   tube(h|l, ir1=|id1=, ir2=|id2=, or1=|od1=, or2=|od2=, ...) [ATTACHMENTS];
1994//   tube(h|l, or1=|od1=, or2=|od2=, wall=, ...) [ATTACHMENTS];
1995// Arguments:
1996//   h / l = height of tube. Default: 1
1997//   or = Outer radius of tube. Default: 1
1998//   ir = Inner radius of tube.
1999//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2000//   ---
2001//   od = Outer diameter of tube.
2002//   id = Inner diameter of tube.
2003//   wall = horizontal thickness of tube wall. Default 1
2004//   or1 = Outer radius of bottom of tube.  Default: value of r)
2005//   or2 = Outer radius of top of tube.  Default: value of r)
2006//   od1 = Outer diameter of bottom of tube.
2007//   od2 = Outer diameter of top of tube.
2008//   ir1 = Inner radius of bottom of tube.
2009//   ir2 = Inner radius of top of tube.
2010//   id1 = Inner diameter of bottom of tube.
2011//   id2 = Inner diameter of top of tube.
2012//   realign = If true, rotate the tube by half the angle of one face.
2013//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2014//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2015//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2016//
2017// Example: These all Produce the Same Tube
2018//   tube(h=30, or=40, wall=5);
2019//   tube(h=30, ir=35, wall=5);
2020//   tube(h=30, or=40, ir=35);
2021//   tube(h=30, od=80, id=70);
2022// Example: These all Produce the Same Conical Tube
2023//   tube(h=30, or1=40, or2=25, wall=5);
2024//   tube(h=30, ir1=35, or2=20, wall=5);
2025//   tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
2026// Example: Circular Wedge
2027//   tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
2028// Example: Standard Connectors
2029//   tube(h=30, or=40, wall=5) show_anchors();
2030
2031function tube(
2032     h, or, ir, center,
2033    od, id, wall,
2034    or1, or2, od1, od2,
2035    ir1, ir2, id1, id2,
2036    realign=false, l, length, height,
2037    anchor, spin=0, orient=UP
2038) = no_function("tube");
2039
2040module tube(
2041    h, or, ir, center,
2042    od, id, wall,
2043    or1, or2, od1, od2,
2044    ir1, ir2, id1, id2,
2045    realign=false, l, length, height,
2046    anchor, spin=0, orient=UP
2047) {
2048    h = one_defined([h,l,height,length],"h,l,height,length",dflt=1);
2049    orr1 = get_radius(r1=or1, r=or, d1=od1, d=od, dflt=undef);
2050    orr2 = get_radius(r1=or2, r=or, d1=od2, d=od, dflt=undef);
2051    irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
2052    irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
2053    wall = default(wall, 1);
2054    r1 = default(orr1, u_add(irr1,wall));
2055    r2 = default(orr2, u_add(irr2,wall));
2056    ir1 = default(irr1, u_sub(orr1,wall));
2057    ir2 = default(irr2, u_sub(orr2,wall));
2058    checks =
2059        assert(all_defined([r1, r2, ir1, ir2]), "Must specify two of inner radius/diam, outer radius/diam, and wall width.")
2060        assert(ir1 <= r1, "Inner radius is larger than outer radius.")
2061        assert(ir2 <= r2, "Inner radius is larger than outer radius.");
2062    sides = segs(max(r1,r2));
2063    anchor = get_anchor(anchor, center, BOT, CENTER);
2064    attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
2065        zrot(realign? 180/sides : 0) {
2066            difference() {
2067                cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
2068                cyl(h=h+0.05, r1=ir1, r2=ir2);
2069            }
2070        }
2071        children();
2072    }
2073}
2074
2075
2076
2077// Function&Module: pie_slice()
2078// Synopsis: Creates a pie slice shape.
2079// SynTags: Geom, VNF
2080// Topics: Shapes (3D), Attachable, VNF Generators
2081// See Also: wedge()
2082// Description:
2083//   Creates a pie slice shape.
2084//
2085// Usage: As Module
2086//   pie_slice(l|h=|height=|length=, r, ang, [center]);
2087//   pie_slice(l|h=|height=|length=, d=, ang=, ...);
2088//   pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2089// Usage: As Function
2090//   vnf = pie_slice(l|h=|height=|length=, r, ang, [center]);
2091//   vnf = pie_slice(l|h=|height=|length=, d=, ang=, ...);
2092//   vnf = pie_slice(l|h=|height=|length=, r1=|d1=, r2=|d2=, ang=, ...);
2093// Usage: Attaching Children
2094//   pie_slice(l|h, r, ang, ...) ATTACHMENTS;
2095//
2096// Arguments:
2097//   h / l / height / length = height of pie slice.
2098//   r = radius of pie slice.
2099//   ang = pie slice angle in degrees.
2100//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=UP`.
2101//   ---
2102//   r1 = bottom radius of pie slice.
2103//   r2 = top radius of pie slice.
2104//   d = diameter of pie slice.
2105//   d1 = bottom diameter of pie slice.
2106//   d2 = top diameter of pie slice.
2107//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2108//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2109//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2110//
2111// Example: Cylindrical Pie Slice
2112//   pie_slice(ang=45, l=20, r=30);
2113// Example: Conical Pie Slice
2114//   pie_slice(ang=60, l=20, d1=50, d2=70);
2115// Example: Big Slice
2116//   pie_slice(ang=300, l=20, d1=50, d2=70);
2117// Example: Generating a VNF
2118//   vnf = pie_slice(ang=150, l=20, r1=30, r2=50);
2119//   vnf_polyhedron(vnf);
2120
2121module pie_slice(
2122    h, r, ang=30, center,
2123    r1, r2, d, d1, d2, l, length, height,
2124    anchor, spin=0, orient=UP
2125) {
2126    l = one_defined([l, h,height,length],"l,h,height,length",dflt=1);
2127    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
2128    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
2129    maxd = max(r1,r2)+0.1;
2130    anchor = get_anchor(anchor, center, BOT, BOT);
2131    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
2132        difference() {
2133            cyl(r1=r1, r2=r2, h=l);
2134            if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2135            difference() {
2136                fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
2137                if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
2138            }
2139        }
2140        children();
2141    }
2142}
2143
2144function pie_slice(
2145    h, r, ang=30, center,
2146    r1, r2, d, d1, d2, l, length, height,
2147    anchor, spin=0, orient=UP
2148) = let(
2149        anchor = get_anchor(anchor, center, BOT, BOT),
2150        l = one_defined([l, h,height,length],"l,h,height,length",dflt=1),
2151        r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10),
2152        r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10),
2153        maxd = max(r1,r2)+0.1,
2154        sides = ceil(segs(max(r1,r2))*ang/360),
2155        step = ang/sides,
2156        vnf = vnf_vertex_array(
2157            points=[
2158                for (u = [0,1]) let(
2159                    h = lerp(-l/2,l/2,u),
2160                    r = lerp(r1,r2,u)
2161                ) [
2162                    for (theta = [0:step:ang+EPSILON])
2163                        cylindrical_to_xyz(r,theta,h),
2164                    [0,0,h]
2165                ]
2166            ],
2167            col_wrap=true, caps=true, reverse=true
2168        )
2169    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
2170
2171
2172
2173// Section: Other Round Objects
2174
2175
2176// Function&Module: sphere()
2177// Synopsis: Creates an attachable spherical object.
2178// SynTags: Geom, VNF, Ext
2179// Topics: Shapes (3D), Attachable, VNF Generators
2180// See Also: spheroid()
2181// Usage: As Module (native OpenSCAD)
2182//   sphere(r|d=);
2183// Usage: Using BOSL2 attachments extensions
2184//   sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2185// Usage: As Function (BOSL2 extension)
2186//   vnf = sphere(r|d=, [anchor=], [spin=], [orient=]) [ATTACHMENTS];
2187// Description:
2188//   Creates a sphere object.
2189//   This module extends the built-in `sphere()` module by providing support for BOSL2 anchoring and attachments, and a function form. 
2190//   When called as a function, returns a [VNF](vnf.scad) for a sphere.
2191// Arguments:
2192//   r = Radius of the sphere.
2193//   ---
2194//   d = Diameter of the sphere.
2195//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2196//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2197//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2198// Example: By Radius
2199//   sphere(r=50);
2200// Example: By Diameter
2201//   sphere(d=100);
2202// Example: Anchoring
2203//   sphere(d=100, anchor=FRONT);
2204// Example: Spin
2205//   sphere(d=100, anchor=FRONT, spin=45);
2206// Example: Orientation
2207//   sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
2208// Example: Standard Connectors
2209//   sphere(d=50) show_anchors();
2210
2211module sphere(r, d, anchor=CENTER, spin=0, orient=UP) {
2212    r = get_radius(r=r, d=d, dflt=1);
2213    attachable(anchor,spin,orient, r=r) {
2214            _sphere(r=r);
2215            children();
2216    }
2217}
2218
2219function sphere(r, d, anchor=CENTER, spin=0, orient=UP) =
2220    spheroid(r=r, d=d, style="orig", anchor=anchor, spin=spin, orient=orient);
2221
2222
2223// Function&Module: spheroid()
2224// Synopsis: Creates an attachable spherical object with controllable triangulation.
2225// SynTags: Geom, VNF
2226// Topics: Shapes (3D), Attachable, VNF Generators
2227// See Also: sphere()
2228// Usage: Typical
2229//   spheroid(r|d, [circum], [style]) [ATTACHMENTS];
2230// Usage: As Function
2231//   vnf = spheroid(r|d, [circum], [style]);
2232// Description:
2233//   Creates a spheroid object, with support for anchoring and attachments.
2234//   This is a drop-in replacement for the built-in `sphere()` module.
2235//   When called as a function, returns a [VNF](vnf.scad) for a spheroid.
2236//   The exact triangulation of this spheroid can be controlled via the `style=`
2237//   argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
2238//   `"octa"`, or `"icosa"`.
2239//   - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
2240//   - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima.  ie: its bounding box is exactly the sphere diameter in length on all three axes.  This is the default.
2241//   - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
2242//   - `style="octa"` forms a sphere by subdividing an octahedron.  This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes.  The effective `$fn` value is quantized to a multiple of 4.  This is used in constructing rounded corners for various other shapes.
2243//   - `style="icosa"` forms a sphere by subdividing an icosahedron.  This makes even more uniform faces over the whole sphere.  The effective `$fn` value is quantized to a multiple of 5.  This sphere has a guaranteed bounding box when `$fn` is a multiple of 10.
2244//   .
2245//   By default the object spheroid() produces is a polyhedron whose vertices all lie on the requested sphere.  This means
2246//   the approximating polyhedron is inscribed in the sphere.
2247//   The `circum` argument requests a circumscribing sphere, where the true sphere is
2248//   inside and tangent to all the faces of the approximating polyhedron.  To produce
2249//   a circumscribing polyhedron, we use the dual polyhedron of the basic form.  The dual of a polyhedron is
2250//   a new polyhedron whose vertices are obtained from the faces of the parent polyhedron.
2251//   The "orig" and "align" forms are duals of each other.  If you request a circumscribing polyhedron in
2252//   these styles then the polyhedron will look the same as the default inscribing form.  But for the other
2253//   styles, the duals are completely different from their parents, and from each other.  Generation of the circumscribed versions (duals)
2254//   for "octa" and "icosa" is fast if you use the module form but can be very slow (several minutes) if you use the functional
2255//   form and choose a large $fn value.
2256//   .
2257//   With style="align", the circumscribed sphere has its maximum radius on the X and Y axes
2258//   but is undersized on the Z axis.  With style="octa" the circumscribed sphere has faces at each axis, so
2259//   the radius on the axes is equal to the specified radius, which is the *minimum* radius of the circumscribed sphere.
2260//   The same thing is true for style="icosa" when $fn is a multiple of 10.  This would enable you to create spherical
2261//   holes with guaranteed on-axis dimensions.
2262// Arguments:
2263//   r = Radius of the spheroid.
2264//   style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "aligned"
2265//   ---
2266//   d = Diameter of the spheroid.
2267//   circum = If true, the approximate sphere circumscribes the true sphere of the requested size.  Otherwise inscribes.  Note that for some styles, the circumscribed sphere looks different than the inscribed sphere.  Default: false (inscribes)
2268//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2269//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2270//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2271// Example: By Radius
2272//   spheroid(r=50);
2273// Example: By Diameter
2274//   spheroid(d=100);
2275// Example: style="orig"
2276//   spheroid(d=100, style="orig", $fn=10);
2277// Example: style="aligned"
2278//   spheroid(d=100, style="aligned", $fn=10);
2279// Example: style="stagger"
2280//   spheroid(d=100, style="stagger", $fn=10);
2281// Example: style="stagger" with circum=true
2282//   spheroid(d=100, style="stagger", circum=true, $fn=10);
2283// Example: style="octa", octahedral based tesselation.  In this style, $fn is quantized to a multiple of 4.
2284//   spheroid(d=100, style="octa", $fn=10);
2285// Example: style="octa", with circum=true, produces mostly very irregular hexagonal faces
2286//   spheroid(d=100, style="octa", circum=true, $fn=16);
2287// Example: style="icosa", icosahedral based tesselation.  In this style, $fn is quantized to a multiple of 5.
2288//   spheroid(d=100, style="icosa", $fn=10);
2289// Example: style="icosa", circum=true.  This style has hexagons and 12 pentagons, similar to (but not the same as) a soccer ball.
2290//   spheroid(d=100, style="icosa", circum=true, $fn=10);
2291// Example: Anchoring
2292//   spheroid(d=100, anchor=FRONT);
2293// Example: Spin
2294//   spheroid(d=100, anchor=FRONT, spin=45);
2295// Example: Orientation
2296//   spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
2297// Example: Standard Connectors
2298//   spheroid(d=50) show_anchors();
2299// Example: Called as Function
2300//   vnf = spheroid(d=100, style="icosa");
2301//   vnf_polyhedron(vnf);
2302// Example: With "orig" the circumscribing sphere has the same form.  The green sphere is a tiny bit oversized so it pokes through the low points in the circumscribed sphere with low $fn.  This demonstrates that these spheres are in fact circumscribing.
2303//   color("green")spheroid(r=10.01, $fn=256);
2304//   spheroid(r=10, style="orig", circum=true, $fn=16);
2305// Example: With "aligned" the same is true: the circumscribing sphere is also aligned, if $fn is divisible by 4.
2306//   color("green")spheroid(r=10.01, $fn=256);
2307//   spheroid(r=10, style="aligned", circum=true, $fn=16);
2308// Example: For the other styles, the circumscribing sphere is different, as shown here with "stagger"
2309//   color("green")spheroid(r=10.01, $fn=256);
2310//   spheroid(r=10, style="stagger", circum=true, $fn=16);
2311// Example: The dual of "octa" that provides the circumscribing sphere has weird asymmetric hexagonal faces:
2312//   color("green")spheroid(r=10.01, $fn=256);
2313//   spheroid(r=10, style="octa", circum=true, $fn=16);
2314// Example: The dual of "icosa" features hexagons and always 12 pentagons:
2315//   color("green")spheroid(r=10.01, $fn=256);
2316//   spheroid(r=10, style="icosa", circum=true, $fn=16);
2317
2318module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP)
2319{
2320    r = get_radius(r=r, d=d, dflt=1);
2321    sides = segs(r);
2322    vsides = ceil(sides/2);
2323    attachable(anchor,spin,orient, r=r) {
2324        if (style=="orig" && !circum) {
2325            merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
2326            path = [
2327                let(a = merids[0]) [0, sin(a)],
2328                for (a=merids) [cos(a), sin(a)],
2329                let(a = last(merids)) [0, sin(a)]
2330            ];
2331            scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
2332        }
2333        // Don't now how to construct faces for these efficiently, so use hull_points, which
2334        // is very much faster than using hull() as happens in the spheroid() function
2335        else if (circum && (style=="octa" || style=="icosa")) {
2336            orig_sphere = spheroid(r,style,circum=false);
2337            dualvert = _dual_vertices(orig_sphere);
2338            hull_points(dualvert,fast=true);
2339        } else {
2340            vnf = spheroid(r=r, circum=circum, style=style);
2341            vnf_polyhedron(vnf, convexity=2);
2342        }
2343        children();
2344    }
2345}
2346
2347
2348// p is a list of 3 points defining a triangle in any dimension.  N is the number of extra points
2349// to add, so output triangle has N+2 points on each side.
2350function _subsample_triangle(p,N) =
2351    [for(i=[0:N+1]) [for (j=[0:N+1-i]) unit(lerp(p[0],p[1],i/(N+1)) + (p[2]-p[0])*j/(N+1))]];
2352
2353
2354// Input should have only triangular faces
2355function _dual_vertices(vnf) =
2356  let(vert=vnf[0])
2357  [for(face=vnf[1])
2358      let(planes = select(vert,face))
2359      //linear_solve3(planes, [for(p=planes) p*p])
2360      linear_solve3(select(planes,0,2), [for(i=[0:2]) planes[i]*planes[i]]) // Handle larger faces, maybe?
2361  ];
2362
2363
2364function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) =
2365    let(
2366        r = get_radius(r=r, d=d, dflt=1),
2367        hsides = segs(r),
2368        vsides = max(2,ceil(hsides/2)),
2369        octa_steps = round(max(4,hsides)/4),
2370        icosa_steps = round(max(5,hsides)/5),
2371        stagger = style=="stagger"
2372     )
2373     circum && style=="orig" ?
2374         let(
2375              orig_sphere = spheroid(r,"aligned",circum=false),
2376              dualvert = zrot(360/hsides/2,_dual_vertices(orig_sphere)),
2377              culledvert = [
2378                              [for(i=[0:2:2*hsides-1]) dualvert[i]],
2379                              for(j=[1:vsides-2])
2380                                 [for(i=[0:2:2*hsides-1]) dualvert[j*2*hsides+i]],
2381                              [for(i=[1:2:2*hsides-1]) dualvert[i]]
2382                           ],
2383              vnf = vnf_vertex_array(culledvert,col_wrap=true,caps=true)
2384          )
2385          [reorient(anchor,spin,orient, r=r, p=vnf[0]), vnf[1]]
2386     :
2387     circum && (style=="octa" || style=="icosa") ?
2388         let(
2389              orig_sphere = spheroid(r,style,circum=false),
2390              dualvert = _dual_vertices(orig_sphere),
2391              faces = hull(dualvert)
2392         )
2393         [reorient(anchor,spin,orient, r=r, p=dualvert), faces]
2394     :
2395     style=="icosa" ?    // subdivide faces of an icosahedron and project them onto a sphere
2396         let(
2397             N = icosa_steps-1,
2398             // construct an icosahedron
2399             icovert=[ for(i=[-1,1], j=[-1,1]) each [[0,i,j*PHI], [i,j*PHI,0], [j*PHI,0,i]]],
2400             icoface = hull(icovert),
2401             // Subsample face 0 of the icosahedron
2402             face0 = select(icovert,icoface[0]),
2403             sampled = r * _subsample_triangle(face0,N),
2404             dir0 = mean(face0),
2405             point0 = face0[0]-dir0,
2406             // Make a rotated copy of the subsampled triangle on each icosahedral face
2407             tri_list = [sampled,
2408                         for(i=[1:1:len(icoface)-1])
2409                 let(face = select(icovert,icoface[i]))
2410                 apply(frame_map(z=mean(face),x=face[0]-mean(face))
2411                        *frame_map(z=dir0,x=point0,reverse=true),
2412                       sampled)],
2413             // faces for the first triangle group
2414             faces = vnf_tri_array(tri_list[0],reverse=true)[1],
2415             size = repeat((N+2)*(N+3)/2,3),
2416             // Expand to full face list
2417             fullfaces = [for(i=idx(tri_list)) each [for(f=faces) f+i*size]],
2418             fullvert = flatten(flatten(tri_list))    // eliminate triangle structure
2419         )
2420         [reorient(anchor,spin,orient, r=r, p=fullvert), fullfaces]
2421     :
2422     let(
2423        verts = circum && style=="stagger" ? _dual_vertices(spheroid(r,style,circum=false))
2424              : circum && style=="aligned" ?
2425                     let(
2426                         orig_sphere = spheroid(r,"orig",circum=false),
2427                         dualvert = _dual_vertices(orig_sphere),
2428                         culledvert = zrot(360/hsides/2,
2429                                           [dualvert[0],
2430                                            for(i=[2:2:len(dualvert)-1]) dualvert[i],
2431                                            dualvert[1]])
2432                      )
2433                      culledvert
2434              : style=="orig"? [
2435                                 for (i=[0:1:vsides-1])
2436                                     let(phi = (i+0.5)*180/(vsides))
2437                                     for (j=[0:1:hsides-1])
2438                                         let(theta = j*360/hsides)
2439                                         spherical_to_xyz(r, theta, phi),
2440                               ]
2441              : style=="aligned" || style=="stagger"?
2442                         [ spherical_to_xyz(r, 0, 0),
2443                           for (i=[1:1:vsides-1])
2444                               let(phi = i*180/vsides)
2445                               for (j=[0:1:hsides-1])
2446                                   let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
2447                                   spherical_to_xyz(r, theta, phi),
2448                           spherical_to_xyz(r, 0, 180)
2449                         ]
2450              : style=="octa"?
2451                      let(
2452                           meridians = [
2453                                        1,
2454                                        for (i = [1:1:octa_steps]) i*4,
2455                                        for (i = [octa_steps-1:-1:1]) i*4,
2456                                        1,
2457                                       ]
2458                      )
2459                      [
2460                       for (i=idx(meridians), j=[0:1:meridians[i]-1])
2461                           spherical_to_xyz(r, j*360/meridians[i], i*180/(len(meridians)-1))
2462                      ]
2463              : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
2464        lv = len(verts),
2465        faces = circum && style=="stagger" ?
2466                     let(ptcount=2*hsides)
2467                     [
2468                       [for(i=[ptcount-2:-2:0]) i],
2469                       for(j=[0:hsides-1])
2470                           [j*2, (j*2+2)%ptcount,ptcount+(j*2+2)%ptcount,ptcount+(j*2+3)%ptcount,ptcount+j*2],
2471                       for(i=[1:vsides-3])
2472                           let(base=ptcount*i)
2473                           for(j=[0:hsides-1])
2474                               i%2==0 ? [base+2*j, base+(2*j+1)%ptcount, base+(2*j+2)%ptcount,
2475                                        base+ptcount+(2*j)%ptcount, base+ptcount+(2*j+1)%ptcount, base+ptcount+(2*j-2+ptcount)%ptcount]
2476                                      : [base+(1+2*j)%ptcount, base+(2*j)%ptcount, base+(2*j+3)%ptcount,
2477                                         base+ptcount+(3+2*j)%ptcount, base+ptcount+(2*j+2)%ptcount,base+ptcount+(2*j+1)%ptcount],
2478                       for(j=[0:hsides-1])
2479                          vsides%2==0
2480                            ? [(j*2+3)%ptcount, j*2+1, lv-ptcount+(2+j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount, lv-ptcount+(4+j*2)%ptcount]
2481                            : [(j*2+3)%ptcount, j*2+1, lv-ptcount+(1+j*2)%ptcount, lv-ptcount+(j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount],
2482                       [for(i=[1:2:ptcount-1]) i],
2483                     ]
2484              : style=="aligned" || style=="stagger" ?  // includes case of aligned with circum == true
2485                     [
2486                       for (i=[0:1:hsides-1])
2487                           let(b2 = lv-2-hsides)
2488                           each [
2489                                 [i+1, 0, ((i+1)%hsides)+1],
2490                                 [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
2491                                ],
2492                       for (i=[0:1:vsides-3], j=[0:1:hsides-1])
2493                           let(base = 1 + hsides*i)
2494                           each (
2495                                 (stagger && i%2!=0)? [
2496                                     [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
2497                                     [base+j, base+(j+1)%hsides, base+hsides+j],
2498                                 ] : [
2499                                     [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
2500                                     [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
2501                                 ]
2502                           )
2503                     ]
2504              : style=="orig"? [
2505                                [for (i=[0:1:hsides-1]) hsides-i-1],
2506                                [for (i=[0:1:hsides-1]) lv-hsides+i],
2507                                for (i=[0:1:vsides-2], j=[0:1:hsides-1])
2508                                    each [
2509                                          [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
2510                                          [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
2511                                    ]
2512                               ]
2513              : /*style=="octa"?*/
2514                     let(
2515                         meridians = [
2516                                      0, 1,
2517                                      for (i = [1:1:octa_steps]) i*4,
2518                                      for (i = [octa_steps-1:-1:1]) i*4,
2519                                      1,
2520                                     ],
2521                         offs = cumsum(meridians),
2522                         pc = last(offs)-1,
2523                         os = octa_steps * 2
2524                     )
2525                     [
2526                      for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
2527                      for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
2528                      for (i=[1:1:octa_steps-1])
2529                          let(m = meridians[i+2]/4)
2530                          for (j=[0:1:3], k=[0:1:m-1])
2531                              let(
2532                                  m1 = meridians[i+1],
2533                                  m2 = meridians[i+2],
2534                                  p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
2535                                  p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
2536                                  p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
2537                                  p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
2538                                  p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
2539                                  p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
2540                                  p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
2541                                  p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
2542                              )
2543                              each [
2544                                    [p1, p4, p3],
2545                                    if (k<m-1) [p1, p2, p4],
2546                                    [p5, p7, p8],
2547                                    if (k<m-1) [p5, p8, p6],
2548                                   ],
2549                     ]
2550    ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
2551
2552
2553
2554// Function&Module: torus()
2555// Synopsis: Creates an attachable torus.
2556// SynTags: Geom, VNF
2557// Topics: Shapes (3D), Attachable, VNF Generators
2558// See Also: spheroid(), cyl()
2559//
2560// Usage: As Module
2561//   torus(r_maj|d_maj, r_min|d_min, [center], ...) [ATTACHMENTS];
2562//   torus(or|od, ir|id, ...) [ATTACHMENTS];
2563//   torus(r_maj|d_maj, or|od, ...) [ATTACHMENTS];
2564//   torus(r_maj|d_maj, ir|id, ...) [ATTACHMENTS];
2565//   torus(r_min|d_min, or|od, ...) [ATTACHMENTS];
2566//   torus(r_min|d_min, ir|id, ...) [ATTACHMENTS];
2567// Usage: As Function
2568//   vnf = torus(r_maj|d_maj, r_min|d_min, [center], ...);
2569//   vnf = torus(or|od, ir|id, ...);
2570//   vnf = torus(r_maj|d_maj, or|od, ...);
2571//   vnf = torus(r_maj|d_maj, ir|id, ...);
2572//   vnf = torus(r_min|d_min, or|od, ...);
2573//   vnf = torus(r_min|d_min, ir|id, ...);
2574//
2575// Description:
2576//   Creates an attachable toroidal shape.
2577//
2578// Figure(2D,Med):
2579//   module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
2580//   r = 75; r2 = 30;
2581//   down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
2582//   color("blue") linear_extrude(height=0.01) {
2583//       dashcirc(r=r,start=15,angle=45);
2584//       dashcirc(r=r-r2, start=90+15, angle=60);
2585//       dashcirc(r=r+r2, start=180+45, angle=30);
2586//       dashcirc(r=r+r2, start=15, angle=30);
2587//   }
2588//   rot(240) color("blue") linear_extrude(height=0.01) {
2589//       stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
2590//       right(r) fwd(9) rot(-240) text("or",size=10,anchor=CENTER);
2591//   }
2592//   rot(135) color("blue") linear_extrude(height=0.01) {
2593//       stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
2594//       right((r-r2)/2) back(8) rot(-135) text("ir",size=10,anchor=CENTER);
2595//   }
2596//   rot(45) color("blue") linear_extrude(height=0.01) {
2597//       stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
2598//       right(r/2) back(8) text("r_maj",size=9,anchor=CENTER);
2599//   }
2600//   rot(30) color("blue") linear_extrude(height=0.01) {
2601//       stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
2602//       right(r+r2/2) fwd(8) text("r_min",size=7,anchor=CENTER);
2603//   }
2604//
2605// Arguments:
2606//   r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
2607//   r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
2608//   center = If given, overrides `anchor`.  A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2609//   ---
2610//   d_maj  = major diameter of torus ring. (use with 'r_min', or 'd_min')
2611//   d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
2612//   or = outer radius of the torus. (use with 'ir', or 'id')
2613//   ir = inside radius of the torus. (use with 'or', or 'od')
2614//   od = outer diameter of the torus. (use with 'ir' or 'id')
2615//   id = inside diameter of the torus. (use with 'or' or 'od')
2616//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2617//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2618//
2619// Example:
2620//   // These all produce the same torus.
2621//   torus(r_maj=22.5, r_min=7.5);
2622//   torus(d_maj=45, d_min=15);
2623//   torus(or=30, ir=15);
2624//   torus(od=60, id=30);
2625//   torus(d_maj=45, id=30);
2626//   torus(d_maj=45, od=60);
2627//   torus(d_min=15, id=30);
2628//   torus(d_min=15, od=60);
2629//   vnf_polyhedron(torus(d_min=15, od=60), convexity=4);
2630// Example: Standard Connectors
2631//   torus(od=60, id=30) show_anchors();
2632
2633module torus(
2634    r_maj, r_min, center,
2635    d_maj, d_min,
2636    or, od, ir, id,
2637    anchor, spin=0, orient=UP
2638) {
2639    _or = get_radius(r=or, d=od, dflt=undef);
2640    _ir = get_radius(r=ir, d=id, dflt=undef);
2641    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
2642    _r_min = get_radius(r=r_min, d=d_min, dflt=undef);
2643    maj_rad = is_finite(_r_maj)? _r_maj :
2644        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2645        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2646        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2647        assert(false, "Bad Parameters");
2648    min_rad = is_finite(_r_min)? _r_min :
2649        is_finite(_ir)? (maj_rad - _ir) :
2650        is_finite(_or)? (_or - maj_rad) :
2651        assert(false, "Bad Parameters");
2652    anchor = get_anchor(anchor, center, BOT, CENTER);
2653    attachable(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2) {
2654        rotate_extrude(convexity=4) {
2655            right_half(s=min_rad*2, planar=true)
2656                right(maj_rad)
2657                    circle(r=min_rad);
2658        }
2659        children();
2660    }
2661}
2662
2663
2664function torus(
2665    r_maj, r_min, center,
2666    d_maj, d_min,
2667    or, od, ir, id,
2668    anchor, spin=0, orient=UP
2669) = let(
2670    _or = get_radius(r=or, d=od, dflt=undef),
2671    _ir = get_radius(r=ir, d=id, dflt=undef),
2672    _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef),
2673    _r_min = get_radius(r=r_min, d=d_min, dflt=undef),
2674    maj_rad = is_finite(_r_maj)? _r_maj :
2675        is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2676        is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2677        is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2678        assert(false, "Bad Parameters"),
2679    min_rad = is_finite(_r_min)? _r_min :
2680        is_finite(_ir)? (maj_rad - _ir) :
2681        is_finite(_or)? (_or - maj_rad) :
2682        assert(false, "Bad Parameters"),
2683    anchor = get_anchor(anchor, center, BOT, CENTER),
2684    maj_sides = segs(maj_rad+min_rad),
2685    maj_step = 360 / maj_sides,
2686    min_sides = segs(min_rad),
2687    min_step = 360 / min_sides,
2688    xyprofile = min_rad <= maj_rad? right(maj_rad, p=circle(r=min_rad)) :
2689        right_half(p=right(maj_rad, p=circle(r=min_rad)))[0],
2690    profile = xrot(90, p=path3d(xyprofile)),
2691    vnf = vnf_vertex_array(
2692        points=[for (a=[0:maj_step:360-EPSILON]) zrot(a, p=profile)],
2693        caps=false, col_wrap=true, row_wrap=true, reverse=true
2694    )
2695) reorient(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2, p=vnf);
2696
2697
2698// Function&Module: teardrop()
2699// Synopsis: Creates a teardrop shape.
2700// SynTags: Geom, VNF
2701// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2702// See Also: onion(), teardrop2d()
2703// Description:
2704//   Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
2705//   Optional chamfers can be added with positive or negative distances.  A positive distance
2706//   specifies the amount to inset the chamfer along the front/back faces of the shape.
2707//   The chamfer will extend the same y distance into the shape.  If the radii are the same
2708//   then the chamfer will be a 45 degree chamfer, but in other cases it will not.
2709//   Note that with caps, the chamfer must not be so big that it makes the cap height illegal.  
2710//
2711// Usage: Typical
2712//   teardrop(h|l=|length=|height=, r, [ang], [cap_h], [chamfer=], ...) [ATTACHMENTS];
2713//   teardrop(h|l=|length=|height=, d=, [ang=], [cap_h=], [chamfer=], ...) [ATTACHMENTS];
2714// Usage: Psuedo-Conical
2715//   teardrop(h|l=|height=|length=, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2716//   teardrop(h|l=|height=|length=, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...)  [ATTACHMENTS];
2717// Usage: As Function
2718//   vnf = teardrop(h|l=|height=|length=, r|d=, [ang=], [cap_h=], ...);
2719//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h=], ...);
2720//   vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h1=], [cap_h2=], ...);
2721//
2722// Arguments:
2723//   h / l / height / length = Thickness of teardrop. Default: 1
2724//   r = Radius of circular part of teardrop.  Default: 1
2725//   ang = Angle of hat walls from the Z axis.  Default: 45 degrees
2726//   cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
2727//   ---
2728//   circum = produce a circumscribing teardrop shape.  Default: false
2729//   r1 = Radius of circular portion of the front end of the teardrop shape.
2730//   r2 = Radius of circular portion of the back end of the teardrop shape.
2731//   d = Diameter of circular portion of the teardrop shape.
2732//   d1 = Diameter of circular portion of the front end of the teardrop shape.
2733//   d2 = Diameter of circular portion of the back end of the teardrop shape.
2734//   cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
2735//   cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
2736//   chamfer = Specifies size of chamfer as distance along the bottom and top faces.  Default: 0
2737//   chamfer1 = Specifies size of chamfer on bottom as distance along bottom face.  Default: 0
2738//   chamfer2 = Specifies size of chamfer on top as distance along top face.  Default: 0
2739//   realign = Passes realign option to teardrop2d, which shifts face alignment.  Default: false
2740//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2741//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2742//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2743//
2744// Extra Anchors:
2745//   cap = The center of the top of the cap, oriented with the cap face normal.
2746//   cap_fwd = The front edge of the cap.
2747//   cap_back = The back edge of the cap.
2748//
2749// Example: Typical Shape
2750//   teardrop(r=30, h=10, ang=30);
2751// Example: Crop Cap
2752//   teardrop(r=30, h=10, ang=30, cap_h=40);
2753// Example: Close Crop
2754//   teardrop(r=30, h=10, ang=30, cap_h=20);
2755// Example: Psuedo-Conical
2756//   teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
2757// Example: Adding chamfers can be useful for a teardrop hole mask
2758//   teardrop(r=10, l=50, chamfer1=2, chamfer2=-1.5);
2759// Example: Getting a VNF
2760//   vnf = teardrop(r1=25, r2=30, l=20, cap_h1=25, cap_h2=35);
2761//   vnf_polyhedron(vnf);
2762// Example: Standard Conical Connectors
2763//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2764//       show_anchors(custom=false);
2765// Example(Spin,VPD=150,Med): Named Conical Connectors
2766//   teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2767//       show_anchors(std=false);
2768
2769module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, length, height, circum=false, realign=false,
2770                chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP)
2771{
2772    length = one_defined([l, h, length, height],"l,h,length,height");
2773    dummy=assert(is_finite(length) && length>0, "length must be positive");
2774    r1 = get_radius(r=r, r1=r1, d=d, d1=d1);
2775    r2 = get_radius(r=r, r1=r2, d=d, d1=d2);
2776    tip_y1 = r1/cos(90-ang);
2777    tip_y2 = r2/cos(90-ang);
2778    _cap_h1 = min(default(cap_h1, tip_y1), tip_y1);
2779    _cap_h2 = min(default(cap_h2, tip_y2), tip_y2);
2780    capvec = unit([0, _cap_h1-_cap_h2, length]);
2781    anchors = [
2782        named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2783        named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2784        named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2785    ];
2786    attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors)
2787    {
2788        vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2,circum=circum,realign=realign,
2789                                length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer));
2790        children();
2791    }
2792}
2793
2794
2795function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2,  chamfer, chamfer1, chamfer2, circum=false, realign=false,
2796                  l, length, height, anchor=CENTER, spin=0, orient=UP) =
2797    let(
2798        r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1),
2799        r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1),
2800        length = one_defined([l, h, length, height],"l,h,length,height"),
2801        dummy0=assert(is_finite(length) && length>0, "length must be positive"),
2802        cap_h1 = first_defined([cap_h1, cap_h]),
2803        cap_h2 = first_defined([cap_h2, cap_h]),
2804        chamfer1 = first_defined([chamfer1,chamfer,0]),
2805        chamfer2 = first_defined([chamfer2,chamfer,0]),    
2806        sides = segs(max(r1,r2)),
2807        profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2808        profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2809        tip_y1 = r1/cos(90-ang),
2810        tip_y2 = r2/cos(90-ang),
2811        _cap_h1 = min(default(cap_h1, tip_y1), tip_y1),
2812        _cap_h2 = min(default(cap_h2, tip_y2), tip_y2),
2813        capvec = unit([0, _cap_h1-_cap_h2, length]),
2814        dummy=
2815          assert(abs(chamfer1)+abs(chamfer2) <= length,"chamfers are too big to fit in the length")
2816          assert(chamfer1<=r1 && chamfer2<=r2, "Chamfers cannot be larger than raduis")
2817          assert(is_undef(cap_h1) || cap_h1-chamfer1 > r1*sin(ang), "chamfer1 is too big to work with the specified cap_h1")
2818          assert(is_undef(cap_h2) || cap_h2-chamfer2 > r2*sin(ang), "chamfer2 is too big to work with the specified cap_h2"),
2819        cprof1 = r1==chamfer1 ? repeat([0,0],len(profile1))
2820                              : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2821        cprof2 = r2==chamfer2 ? repeat([0,0],len(profile2))
2822                              : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2823        anchors = [
2824            named_anchor("cap",      [0,0,(_cap_h1+_cap_h2)/2], capvec),
2825            named_anchor("cap_fwd",  [0,-length/2,_cap_h1],         unit((capvec+FWD)/2)),
2826            named_anchor("cap_back", [0,+length/2,_cap_h2],         unit((capvec+BACK)/2), 180),
2827        ],
2828        vnf = vnf_vertex_array(
2829            points = [
2830                if (chamfer1!=0) fwd(length/2, xrot(90, path3d(cprof1))),
2831                fwd(length/2-abs(chamfer1), xrot(90, path3d(profile1))),
2832                back(length/2-abs(chamfer2), xrot(90, path3d(profile2))),
2833                if (chamfer2!=0) back(length/2, xrot(90, path3d(cprof2))),
2834            ],
2835            caps=true, col_wrap=true, reverse=true
2836        )
2837    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors, p=vnf);
2838
2839
2840// Function&Module: onion()
2841// Synopsis: Creates an attachable onion-like shape.
2842// SynTags: Geom, VNF
2843// Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized
2844// See Also: teardrop(), teardrop2d()
2845// Description:
2846//   Creates a sphere with a conical hat, to make a 3D teardrop.
2847//
2848// Usage: As Module
2849//   onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...) [ATTACHMENTS];
2850// Usage: As Function
2851//   vnf = onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...);
2852//
2853// Arguments:
2854//   r = radius of spherical portion of the bottom. Default: 1
2855//   ang = Angle of cone on top from vertical. Default: 45 degrees
2856//   cap_h = If given, height above sphere center to truncate teardrop shape.  Default: `undef` (no truncation)
2857//   ---
2858//   circum = set to true to circumscribe the specified radius/diameter.  Default: False
2859//   realign = adjust point alignment to determine if bottom is flat or pointy.  Default: False
2860//   d = diameter of spherical portion of bottom.
2861//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2862//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2863//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2864//
2865// Extra Anchors:
2866//   cap = The center of the top of the cap, oriented with the cap face normal.
2867//   tip = The position where an un-capped onion would come to a point, oriented in the direction the point is from the center.
2868//
2869// Example: Typical Shape
2870//   onion(r=30, ang=30);
2871// Example: Crop Cap
2872//   onion(r=30, ang=30, cap_h=40);
2873// Example: Close Crop
2874//   onion(r=30, ang=30, cap_h=20);
2875// Example: Onions are useful for making the tops of large cylindrical voids.
2876//   difference() {
2877//       cuboid([100,50,100], anchor=FWD+BOT);
2878//       down(0.1)
2879//           cylinder(h=50,d=50,anchor=BOT)
2880//               attach(TOP)
2881//                   onion(d=50, cap_h=30);
2882//   }
2883// Example: Standard Connectors
2884//   onion(d=30, ang=30, cap_h=20) show_anchors();
2885
2886module onion(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, orient=UP)
2887{
2888    r = get_radius(r=r, d=d, dflt=1);
2889    xyprofile = teardrop2d(r=r, ang=ang, cap_h=cap_h, circum=circum, realign=realign);
2890    tip_h = max(column(xyprofile,1));
2891    _cap_h = min(default(cap_h,tip_h), tip_h);
2892    anchors = [
2893        ["cap", [0,0,_cap_h], UP, 0],
2894        ["tip", [0,0,tip_h], UP, 0]
2895    ];
2896    attachable(anchor,spin,orient, r=r, anchors=anchors) {
2897        rotate_extrude(convexity=2) {
2898            difference() {
2899                polygon(xyprofile);
2900                square([2*r,2*max(_cap_h,r)+1], anchor=RIGHT);
2901            }
2902        }
2903        children();
2904    }
2905}
2906
2907
2908function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
2909    let(
2910        r = get_radius(r=r, d=d, dflt=1),
2911        xyprofile = right_half(p=teardrop2d(r=r, ang=ang, cap_h=cap_h))[0],
2912        profile = xrot(90, p=path3d(xyprofile)),
2913        tip_h = max(column(xyprofile,1)),
2914        _cap_h = min(default(cap_h,tip_h), tip_h),
2915        anchors = [
2916            ["cap", [0,0,_cap_h], UP, 0],
2917            ["tip", [0,0,tip_h], UP, 0]
2918        ],
2919        sides = segs(r),
2920        step = 360 / sides,
2921        vnf = vnf_vertex_array(
2922            points=[for (a = [0:step:360-EPSILON]) zrot(a, p=profile)],
2923            caps=false, col_wrap=true, row_wrap=true, reverse=true
2924        )
2925    ) reorient(anchor,spin,orient, r=r, anchors=anchors, p=vnf);
2926
2927
2928// Section: Text
2929
2930// Module: text3d()
2931// Synopsis: Creates an attachable 3d text block.
2932// SynTags: Geom
2933// Topics: Attachments, Text
2934// See Also: path_text(), text() 
2935// Usage:
2936//   text3d(text, [h], [size], [font], [language=], [script=], [direction=], [atype=], [anchor=], [spin=], [orient=]);
2937// Description:
2938//   Creates a 3D text block that supports anchoring and attachment to attachable objects.  You cannot attach children to text.
2939//   .
2940//   Historically fonts were specified by their "body size", the height of the metal body
2941//   on which the glyphs were cast.  This means the size was an upper bound on the size
2942//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
2943//   the metal body is replaced by an invisible box, the em square, whose side length is
2944//   defined to be the font's size.  The glyphs can be contained in that square, or they
2945//   can extend beyond it, depending on the choices made by the font designer.  As a
2946//   result, the meaning of font size varies between fonts: two fonts at the "same" size
2947//   can differ significantly in the actual size of their characters.  Typographers
2948//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
2949//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
2950//   printing), so if you want points you will need to perform a suitable unit conversion.
2951//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
2952//   instead get a font whose size is s/0.72.  For many fonts this means the size of
2953//   capital letters will be approximately equal to s, because it is common for fonts to
2954//   use about 70% of their height for the ascenders in the font.  To get the customary
2955//   font size, you should multiply your desired size by 0.72.
2956//   .
2957//   To find the fonts that you have available in your OpenSCAD installation,
2958//   go to the Help menu and select "Font List".
2959// Arguments:
2960//   text = Text to create.
2961//   h / height / thickness = Extrusion height for the text.  Default: 1
2962//   size = The font will be created at this size divided by 0.72.   Default: 10
2963//   font = Font to use.  Default: "Liberation Sans" (standard OpenSCAD default)
2964//   ---
2965//   spacing = The relative spacing multiplier between characters.  Default: `1.0`
2966//   direction = The text direction.  `"ltr"` for left to right.  `"rtl"` for right to left. `"ttb"` for top to bottom. `"btt"` for bottom to top.  Default: `"ltr"`
2967//   language = The language the text is in.  Default: `"en"`
2968//   script = The script the text is in.  Default: `"latin"`
2969//   atype = Change vertical center between "baseline" and "ycenter".  Default: "baseline"
2970//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `"baseline"`
2971//   center = Center the text.  Equivalent to `atype="center", anchor=CENTER`.  Default: false
2972//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2973//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2974// Anchor Types:
2975//   baseline = Anchor center is relative to text baseline
2976//   ycenter = Anchor center is relative to the actualy y direction center of the text
2977// Examples:
2978//   text3d("Fogmobar", h=3, size=10);
2979//   text3d("Fogmobar", h=2, size=12, font="Helvetica");
2980//   text3d("Fogmobar", h=2, anchor=CENTER);
2981//   text3d("Fogmobar", h=2, anchor=CENTER, atype="ycenter");
2982//   text3d("Fogmobar", h=2, anchor=RIGHT);
2983//   text3d("Fogmobar", h=2, anchor=RIGHT+BOT, atype="ycenter");
2984module text3d(text, h, size=10, font="Helvetica", spacing=1.0, direction="ltr", language="en", script="latin",
2985              height, thickness, atype, center=false,
2986              anchor, spin=0, orient=UP) {
2987    no_children($children);
2988    h = one_defined([h,height,thickness],"h,height,thickness",dflt=1);
2989    assert(is_undef(atype) || in_list(atype,["ycenter","baseline"]), "atype must be \"ycenter\" or \"baseline\"");
2990    assert(is_bool(center));
2991    atype = default(atype, center?"ycenter":"baseline");
2992    anchor = default(anchor, center?CENTER:LEFT);
2993    geom = attach_geom(size=[size,size,h]);
2994    ha = anchor.x<0? "left" 
2995       : anchor.x>0? "right" 
2996       : "center";
2997    va = anchor.y<0? "bottom" 
2998       : anchor.y>0? "top" 
2999       : atype=="baseline"? "baseline"
3000       : "center";
3001    m = _attach_transform([0,0,anchor.z],spin,orient,geom);
3002    multmatrix(m) {
3003        $parent_anchor = anchor;
3004        $parent_spin   = spin;
3005        $parent_orient = orient;
3006        $parent_geom   = geom;
3007        $parent_size   = _attach_geom_size(geom);
3008        $attach_to   = undef;
3009        if (_is_shown()) {
3010            _color($color) {
3011                linear_extrude(height=h, center=true)
3012                    _text(
3013                        text=text, size=size, font=font,
3014                        halign=ha, valign=va, spacing=spacing,
3015                        direction=direction, language=language,
3016                        script=script
3017                    );
3018            }
3019        }
3020    }
3021}
3022
3023
3024// This could be replaced with _cut_to_seg_u_form
3025function _cut_interp(pathcut, path, data) =
3026  [for(entry=pathcut)
3027    let(
3028       a = path[entry[1]-1],
3029        b = path[entry[1]],
3030        c = entry[0],
3031        i = max_index(v_abs(b-a)),
3032        factor = (c[i]-a[i])/(b[i]-a[i])
3033    )
3034    (1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
3035  ];
3036
3037
3038// Module: path_text()
3039// Synopsis: Creates 2d or 3d text placed along a path.
3040// SynTags: Geom
3041// Topics: Text, Paths, Paths (2D), Paths (3D), Path Generators, Path Generators (2D)
3042// See Also, text(), text2d()
3043// Usage:
3044//   path_text(path, text, [size], [thickness], [font], [lettersize=], [offset=], [reverse=], [normal=], [top=], [textmetrics=], [kern=])
3045// Description:
3046//   Place the text letter by letter onto the specified path using textmetrics (if available and requested)
3047//   or user specified letter spacing.  The path can be 2D or 3D.  In 2D the text appears along the path with letters upright
3048//   as determined by the path direction.  In 3D by default letters are positioned on the tangent line to the path with the path normal
3049//   pointing toward the reader.  The path normal points away from the center of curvature (the opposite of the normal produced
3050//   by path_normals()).  Note that this means that if the center of curvature switches sides the text will flip upside down.
3051//   If you want text on such a path you must supply your own normal or top vector.
3052//   .
3053//   Text appears starting at the beginning of the path, so if the 3D path moves right to left
3054//   then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
3055//   The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the
3056//   curve from the center of curvature).  If you need the text to read properly from the inside, you can set reverse to
3057//   true to flip the text, or supply your own normal.
3058//   .
3059//   If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
3060//   using lettersize, which can be a scalar or array.  You will have the easiest time getting good results by using
3061//   a monospace font such as Courier.  Note that even with text metrics, spacing may be different because path_text()
3062//   doesn't do kerning to adjust positions of individual glyphs.  Also if your font has ligatures they won't be used.
3063//   .
3064//   By default letters appear centered on the path.  The offset can be specified to shift letters toward the reader (in
3065//   the direction of the normal).
3066//   .
3067//   You can specify your own normal by setting `normal` to a direction or a list of directions.  Your normal vector should
3068//   point toward the reader.  You can also specify
3069//   top, which directs the top of the letters in a desired direction.  If you specify your own directions and they
3070//   are not perpendicular to the path then the direction you specify will take priority and the
3071//   letters will not rest on the tangent line of the path.  Note that the normal or top directions that you
3072//   specify must not be parallel to the path.
3073//   .
3074//   Historically fonts were specified by their "body size", the height of the metal body
3075//   on which the glyphs were cast.  This means the size was an upper bound on the size
3076//   of the font glyphs, not a direct measurement of their size.  In digital typesetting,
3077//   the metal body is replaced by an invisible box, the em square, whose side length is
3078//   defined to be the font's size.  The glyphs can be contained in that square, or they
3079//   can extend beyond it, depending on the choices made by the font designer.  As a
3080//   result, the meaning of font size varies between fonts: two fonts at the "same" size
3081//   can differ significantly in the actual size of their characters.  Typographers
3082//   customarily specify the size in the units of "points".  A point is 1/72 inch.  In
3083//   OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
3084//   printing), so if you want points you will need to perform a suitable unit conversion.
3085//   In addition, the OpenSCAD font system has a bug: if you specify size=s you will
3086//   instead get a font whose size is s/0.72.  For many fonts this means the size of
3087//   capital letters will be approximately equal to s, because it is common for fonts to
3088//   use about 70% of their height for the ascenders in the font.  To get the customary
3089//   font size, you should multiply your desired size by 0.72.
3090//   .
3091//   To find the fonts that you have available in your OpenSCAD installation,
3092//   go to the Help menu and select "Font List".
3093// Arguments:
3094//   path = path to place the text on
3095//   text = text to create
3096//   size = The font will be created at this size divided by 0.72.   
3097//   thickness / h / height = thickness of letters (not allowed for 2D path)
3098//   font = font to use.  Default: "Liberation Sans"
3099//   ---
3100//   lettersize = scalar or array giving size of letters
3101//   center = center text on the path instead of starting at the first point.  Default: false
3102//   offset = distance to shift letters "up" (towards the reader).  Not allowed for 2D path.  Default: 0
3103//   normal = direction or list of directions pointing towards the reader of the text.  Not allowed for 2D path.
3104//   top = direction or list of directions pointing toward the top of the text
3105//   reverse = reverse the letters if true.  Not allowed for 2D path.  Default: false
3106//   textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature.  You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences.  Default: false
3107//   valign = align text to the path using "top", "bottom", "center" or "baseline".  You can also adjust position with a numerical offset as in "top-5" or "bottom+2".  This only works with textmetrics enabled.  You can give a simple numerical offset, which will be relative to the baseline and works even without textmetrics.  Default: "baseline"
3108//   kern = scalar or array giving spacing adjusments between each letter.  If it's an array it should have one less entry than the text string.  Default: 0
3109//   language = text language, passed to OpenSCAD `text()`.  Default: "en"
3110//   script = text script, passed to OpenSCAD `text()`.  Default: "latin" 
3111// Example(3D,NoScales):  The examples use Courier, a monospaced font.  The width is 1/1.2 times the specified size for this font.  This text could wrap around a cylinder.
3112//   path = path3d(arc(100, r=25, angle=[245, 370]));
3113//   color("red")stroke(path, width=.3);
3114//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
3115// Example(3D,NoScales): By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
3116//   path = path3d(arc(100, r=25, angle=[245, 370]));
3117//   color("red")stroke(path, width=.3);
3118//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
3119// Example(3D,NoScales):  If we want text that reads from the other side we can use reverse.  Note we have to reverse the direction of the path and also set the reverse option.
3120//   path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
3121//   color("red")stroke(path, width=.3);
3122//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
3123// Example(3D,Med,NoScales): text debossed onto a cylinder in a spiral.  The text is 1 unit deep because it is half in, half out.
3124//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3125//   L = 5*len(text);
3126//   maxang = 360*L/(PI*50);
3127//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3128//   difference(){
3129//     cyl(d=50, l=50, $fn=120);
3130//     path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3131//   }
3132// Example(3D,Med,NoScales): Same example but text embossed.  Make sure you have enough depth for the letters to fully overlap the object.
3133//   text = ("A long text example to wrap around a cylinder, possibly for a few times.");
3134//   L = 5*len(text);
3135//   maxang = 360*L/(PI*50);
3136//   spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
3137//   cyl(d=50, l=50, $fn=120);
3138//   path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
3139// Example(3D,NoScales): Here the text baseline sits on the path.  (Note the default orientation makes text readable from below, so we specify the normal.)
3140//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3141//   color("red")stroke(path,width=.2);
3142//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
3143// Example(3D,NoScales): If we use top to orient the text upward, the text baseline is no longer aligned with the path.
3144//   path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
3145//   color("red")stroke(path,width=.2);
3146//   path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
3147// Example(3D,Med,NoScales): This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout.  We fix it with a custom normal which is different at every path point.
3148//   path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
3149//   normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
3150//   zrot(-120)
3151//   difference(){
3152//     cyl(r=25, h=20, $fn=120);
3153//     path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
3154//   }
3155// Example(3D,Med,NoScales): The path center of curvature changes, and the text flips.
3156//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3157//   color("red")stroke(path,width=.2);
3158//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2);
3159// Example(3D,Med,NoScales): We can fix it with top:
3160//   path =  zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
3161//   color("red")stroke(path,width=.2);
3162//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
3163// Example(2D,NoScales): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
3164//   path =  zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
3165//   color("red")stroke(path,width=.2);
3166//   path_text(path, "A shorter example",  size=5, lettersize=5/1.2, font="Courier");
3167// Example(3D,NoScales): The kern parameter lets you adjust the letter spacing either with a uniform value for each letter, or with an array to make adjustments throughout the text.  Here we show a case where adding some extra space gives a better look in a tight circle.  When textmetrics are off, `lettersize` can do this job, but with textmetrics, you'll need to use `kern` to make adjustments relative to the text metric sizes.
3168//   path = path3d(arc(100, r=12, angle=[150, 450]));
3169//   color("red")stroke(path, width=.3);
3170//   kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1];
3171//   path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP);
3172
3173module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false,
3174                 textmetrics=false, kern=0, height,h, valign="baseline", language, script)
3175{
3176  no_children($children);
3177  dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
3178         assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"")
3179         assert(all_positive([size]), "Must give positive text size");
3180  dim = len(path[0]);
3181  normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
3182  topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
3183                        || (is_path(top,dim) && len(top)==len(path));
3184  dummy4 = assert(dim==3 || !any_defined([thickness,h,height]), "Cannot give a thickness or height with 2d path")
3185           assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
3186           assert(dim==3 || offset==0, "Cannot give offset with 2d path")
3187           assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
3188           assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
3189           assert(topok,"\"top\" must be a vector or path compatible with the given path");
3190  thickness = one_defined([thickness,h,height],"thickness,h,height",dflt=1);
3191  normal = is_vector(normal) ? repeat(normal, len(path))
3192         : is_def(normal) ? normal
3193         : undef;
3194
3195  top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
3196         : is_def(top) ? top
3197         : undef;
3198
3199  kern = force_list(kern, len(text)-1);
3200  dummy3 = assert(is_list(kern) && len(kern)==len(text)-1, "kern must be a scalar or list whose length is len(text)-1");
3201
3202  lsize = is_def(lettersize) ? force_list(lettersize, len(text))
3203        : textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
3204        : assert(false, "textmetrics disabled: Must specify letter size");
3205  lcenter = convolve(lsize,[1,1]/2)+[0,each kern,0] ;
3206  textlength = sum(lsize)+sum(kern);
3207
3208  ascent = !textmetrics ? undef
3209         : textmetrics(text, font=font, size=size).ascent;
3210  descent = !textmetrics ? undef
3211          : textmetrics(text, font=font, size=size).descent;
3212
3213  vadjustment = is_num(valign) ? -valign
3214              : !textmetrics ? assert(valign=="baseline","valign requires textmetrics support") 0
3215              : let(
3216                     table = [
3217                              ["baseline", 0],
3218                              ["top", -ascent],
3219                              ["bottom", descent],
3220                              ["center", (descent-ascent)/2]
3221                             ],
3222                     match = [for(i=idx(table)) if (starts_with(valign,table[i][0])) i]
3223                )
3224                assert(len(match)==1, "Invalid valign value")
3225                table[match[0]][1] - parse_num(substr(valign,len(table[match[0]][0])));
3226
3227  dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");
3228
3229  start = center ? (path_length(path) - textlength)/2 : 0;
3230   
3231  pts = path_cut_points(path, add_scalar(cumsum(lcenter),start), direction=true);
3232
3233  usernorm = is_def(normal);
3234  usetop = is_def(top);
3235  normpts = is_undef(normal) ? (reverse?1:-1)*column(pts,3) : _cut_interp(pts,path, normal);
3236  toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
3237  attachable(){
3238    for (i = idx(text)) {
3239      tangent = pts[i][2];
3240      checks =
3241          assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
3242                 str("Specified top direction parallel to path at character ",i))
3243          assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
3244                 str("Specified normal direction parallel to path at character ",i));
3245      adjustment = usetop ?  (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
3246                 : usernorm ?  (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
3247                 : [0,0,0];
3248      move(pts[i][0]) {
3249        if (dim==3) {
3250          frame_map(
3251            x=tangent-adjustment,
3252            z=usetop ? undef : normpts[i],
3253            y=usetop ? toppts[i] : undef
3254          ) up(offset-thickness/2) {
3255            linear_extrude(height=thickness)
3256              back(vadjustment)
3257              {
3258              left(lsize[i]/2)
3259                text(text[i], font=font, size=size, language=language, script=script);
3260              }
3261          }
3262        } else {
3263            frame_map(
3264              x=point3d(tangent-adjustment),
3265              y=point3d(usetop ? toppts[i] : -normpts[i])
3266            ) left(lsize[0]/2) {
3267                text(text[i], font=font, size=size, language=language, script=script);
3268            }
3269        }
3270      }
3271    }
3272    union();
3273  }
3274}
3275
3276
3277
3278// Section: Miscellaneous
3279
3280
3281
3282
3283// Module: fillet()
3284// Synopsis: Creates a smooth fillet between two faces.
3285// SynTags: Geom, VNF
3286// Topics: Shapes (3D), Attachable
3287// See Also: mask2d_roundover()
3288// Description:
3289//   Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
3290//   Center this part along the concave edge to be chamfered and union it in.
3291//
3292// Usage: Typical
3293//   fillet(l, r, [ang], [overlap], ...) [ATTACHMENTS];
3294//   fillet(l|length=|h=|height=, d=, [ang=], [overlap=], ...) [ATTACHMENTS];
3295//
3296// Arguments:
3297//   l / length / h / height = Length of edge to fillet.
3298//   r = Radius of fillet.
3299//   ang = Angle between faces to fillet.
3300//   overlap = Overlap size for unioning with faces.
3301//   ---
3302//   d = Diameter of fillet.
3303//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `FRONT+LEFT`
3304//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3305//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3306//
3307// Example:
3308//   union() {
3309//     translate([0,2,-4])
3310//       cube([20, 4, 24], anchor=BOTTOM);
3311//     translate([0,-10,-4])
3312//       cube([20, 20, 4], anchor=BOTTOM);
3313//     color("green")
3314//       fillet(
3315//         l=20, r=10,
3316//         spin=180, orient=RIGHT
3317//       );
3318//   }
3319//
3320// Examples:
3321//   fillet(l=10, r=20, ang=60);
3322//   fillet(l=10, r=20, ang=90);
3323//   fillet(l=10, r=20, ang=120);
3324//
3325// Example: Using with Attachments
3326//   cube(50,center=true) {
3327//     position(FRONT+LEFT)
3328//       fillet(l=50, r=10, spin=-90);
3329//     position(BOT+FRONT)
3330//       fillet(l=50, r=10, spin=180, orient=RIGHT);
3331//   }
3332
3333module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP)
3334{
3335    deprecate("fillet");
3336    fillet(l,r,ang,overlap,d,length,h,height,anchor,spin,orient);
3337}
3338
3339
3340module fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP) {
3341    l = one_defined([l,length,h,height],"l,length,h,height");
3342    r = get_radius(r=r, d=d, dflt=1);
3343    steps = ceil(segs(r)*(180-ang)/360);
3344    arc = arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]);
3345    maxx = last(arc).x;
3346    maxy = arc[0].y;
3347    path = [
3348        [maxx, -overlap],
3349        polar_to_xy(overlap, 180+ang/2),
3350        arc[0] + polar_to_xy(overlap, 90+ang),
3351        each arc
3352    ];
3353    override = function (anchor)
3354        anchor.x>=0 && anchor.y>=0 ? undef
3355      : 
3356        [[max(0,anchor.x)*maxx, max(0,anchor.y)*maxy, anchor.z*l/2]];
3357    attachable(anchor,spin,orient, size=[2*maxx,2*maxy,l],override=override) {      
3358        if (l > 0) {
3359            linear_extrude(height=l, convexity=4, center=true) {
3360                polygon(path);
3361            }
3362        }
3363        children();
3364    }
3365}
3366
3367
3368// Function&Module: heightfield()
3369// Synopsis: Generates a 3D surface from a 2D grid of values.
3370// SynTags: Geom, VNF
3371// Topics: Textures, Heightfield
3372// See Also: cylindrical_heightfield()
3373// Usage: As Module
3374//   heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], [convexity], ...) [ATTACHMENTS];
3375// Usage: As Function
3376//   vnf = heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], ...);
3377// Description:
3378//   Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
3379//   surface where the height at any given point is the scalar value for that position.
3380//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3381//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3382// Arguments:
3383//   data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
3384//   size = The [X,Y] size of the surface to create.  If given as a scalar, use it for both X and Y sizes. Default: `[100,100]`
3385//   bottom = The Z coordinate for the bottom of the heightfield object to create.  Any heights lower than this will be truncated to very slightly above this height.  Default: -20
3386//   maxz = The maximum height to model.  Truncates anything taller to this height.  Set to INF for no truncation.  Default: 100
3387//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3388//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3389//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3390//   ---
3391//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3392//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3393//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3394//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3395// Example:
3396//   heightfield(size=[100,100], bottom=-20, data=[
3397//       for (y=[-180:4:180]) [
3398//           for(x=[-180:4:180])
3399//           10*cos(3*norm([x,y]))
3400//       ]
3401//   ]);
3402// Example:
3403//   intersection() {
3404//       heightfield(size=[100,100], data=[
3405//           for (y=[-180:5:180]) [
3406//               for(x=[-180:5:180])
3407//               10+5*cos(3*x)*sin(3*y)
3408//           ]
3409//       ]);
3410//       cylinder(h=50,d=100);
3411//   }
3412// Example: Heightfield by Function
3413//   fn = function (x,y) 10*sin(x*360)*cos(y*360);
3414//   heightfield(size=[100,100], data=fn);
3415// Example: Heightfield by Function, with Specific Ranges
3416//   fn = function (x,y) 2*cos(5*norm([x,y]));
3417//   heightfield(
3418//       size=[100,100], bottom=-20, data=fn,
3419//       xrange=[-180:2:180], yrange=[-180:2:180]
3420//   );
3421
3422module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
3423{
3424    size = is_num(size)? [size,size] : point2d(size);
3425    vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
3426    attachable(anchor,spin,orient, vnf=vnf) {
3427        vnf_polyhedron(vnf, convexity=convexity);
3428        children();
3429    }
3430}
3431
3432
3433function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", anchor=CENTER, spin=0, orient=UP) =
3434    assert(is_list(data) || is_function(data))
3435    let(
3436        size = is_num(size)? [size,size] : point2d(size),
3437        xvals = is_list(data)
3438          ? [for (i=idx(data[0])) i]
3439          : assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
3440        yvals = is_list(data)
3441          ? [for (i=idx(data)) i]
3442          : assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
3443        xcnt = len(xvals),
3444        minx = min(xvals),
3445        maxx = max(xvals),
3446        ycnt = len(yvals),
3447        miny = min(yvals),
3448        maxy = max(yvals),
3449        verts = is_list(data) ? [
3450                for (y = [0:1:ycnt-1]) [
3451                    for (x = [0:1:xcnt-1]) [
3452                        size.x * (x/(xcnt-1)-0.5),
3453                        size.y * (y/(ycnt-1)-0.5),
3454                        min(data[y][x],maxz)
3455                    ]
3456                ]
3457            ] : [
3458                for (y = yrange) [
3459                    for (x = xrange) let(
3460                        z = data(x,y)
3461                    ) [
3462                        size.x * ((x-minx)/(maxx-minx)-0.5),
3463                        size.y * ((y-miny)/(maxy-miny)-0.5),
3464                        min(maxz, max(bottom+0.1, default(z,0)))
3465                    ]
3466                ]
3467            ],
3468        vnf = vnf_join([
3469            vnf_vertex_array(verts, style=style, reverse=true),
3470            vnf_vertex_array([
3471                verts[0],
3472                [for (v=verts[0]) [v.x, v.y, bottom]],
3473            ]),
3474            vnf_vertex_array([
3475                [for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
3476                verts[ycnt-1],
3477            ]),
3478            vnf_vertex_array([
3479                [for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
3480                [for (r=verts) let(v=r[0]) v],
3481            ]),
3482            vnf_vertex_array([
3483                [for (r=verts) let(v=r[xcnt-1]) v],
3484                [for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
3485            ]),
3486            vnf_vertex_array([
3487                [
3488                    for (v=verts[0]) [v.x, v.y, bottom],
3489                    for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
3490                ], [
3491                    for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
3492                    for (v=verts[ycnt-1]) [v.x, v.y, bottom],
3493                ]
3494            ])
3495        ])
3496    ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
3497
3498
3499// Function&Module: cylindrical_heightfield()
3500// Synopsis: Generates a cylindrical 3d surface from a 2D grid of values.
3501// SynTags: Geom, VNF
3502// Topics: Extrusion, Textures, Knurling, Heightfield
3503// See Also: heightfield()
3504// Usage: As Function
3505//   vnf = cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]);
3506// Usage: As Module
3507//   cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]) [ATTACHMENTS];
3508// Description:
3509//   Given a regular rectangular 2D grid of scalar values, or a function literal of signature (x,y), generates
3510//   a cylindrical 3D surface where the height at any given point above the radius `r=`, is the scalar value
3511//   for that position.
3512//   One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3513//   https://raw.githubusercontent.com/BelfrySCAD/BOSL2/master/scripts/img2scad.py
3514// Arguments:
3515//   data = This is either the 2D rectangular array of heights, or a function literal of signature `(x, y)`.
3516//   l / length / h / height = The length of the cylinder to wrap around.
3517//   r = The radius of the cylinder to wrap around.
3518//   ---
3519//   r1 = The radius of the bottom of the cylinder to wrap around.
3520//   r2 = The radius of the top of the cylinder to wrap around.
3521//   d = The diameter of the cylinder to wrap around.
3522//   d1 = The diameter of the bottom of the cylinder to wrap around.
3523//   d2 = The diameter of the top of the cylinder to wrap around.
3524//   base = The radius for the bottom of the heightfield object to create.  Any heights smaller than this will be truncated to very slightly above this height.  Default: -20
3525//   transpose = If true, swaps the radial and length axes of the data.  Default: false
3526//   aspect = The aspect ratio of the generated heightfield at the surface of the cylinder.  Default: 1
3527//   xrange = A range of values to iterate X over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3528//   yrange = A range of values to iterate Y over when calculating a surface from a function literal.  Default: [-1 : 0.01 : 1]
3529//   maxh = The maximum height above the radius to model.  Truncates anything taller to this height.  Default: 99
3530//   style = The style of subdividing the quads into faces.  Valid options are "default", "alt", and "quincunx".  Default: "default"
3531//   convexity = Max number of times a line could intersect a wall of the surface being formed. Module only.  Default: 10
3532//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3533//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3534//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3535// Example(VPD=400;VPR=[55,0,150]):
3536//   cylindrical_heightfield(l=100, r=30, base=5, data=[
3537//       for (y=[-180:4:180]) [
3538//           for(x=[-180:4:180])
3539//           5*cos(5*norm([x,y]))+5
3540//       ]
3541//   ]);
3542// Example(VPD=400;VPR=[55,0,150]):
3543//   cylindrical_heightfield(l=100, r1=60, r2=30, base=5, data=[
3544//       for (y=[-180:4:180]) [
3545//           for(x=[-180:4:180])
3546//           5*cos(5*norm([x,y]))+5
3547//       ]
3548//   ]);
3549// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function
3550//   fn = function (x,y) 5*sin(x*360)*cos(y*360)+5;
3551//   cylindrical_heightfield(l=100, r=30, data=fn);
3552// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function, with Specific Ranges
3553//   fn = function (x,y) 2*cos(5*norm([x,y]));
3554//   cylindrical_heightfield(
3555//       l=100, r=30, base=5, data=fn,
3556//       xrange=[-180:2:180], yrange=[-180:2:180]
3557//   );
3558
3559function cylindrical_heightfield(
3560    data, l, r, base=1,
3561    transpose=false, aspect=1,
3562    style="min_edge", maxh=99,
3563    xrange=[-1:0.01:1],
3564    yrange=[-1:0.01:1],
3565    r1, r2, d, d1, d2, h, height, length, 
3566    anchor=CTR, spin=0, orient=UP
3567) =
3568    let(
3569        l = one_defined([l, h, height, length], "l,h,height,l"),
3570        r1 = get_radius(r1=r1, r=r, d1=d1, d=d),
3571        r2 = get_radius(r1=r2, r=r, d1=d2, d=d)
3572    )
3573    assert(is_finite(l) && l>0, "Must supply one of l=, h=, or height= as a finite positive number.")
3574    assert(is_finite(r1) && r1>0, "Must supply one of r=, r1=, d=, or d1= as a finite positive number.")
3575    assert(is_finite(r2) && r2>0, "Must supply one of r=, r2=, d=, or d2= as a finite positive number.")
3576    assert(is_finite(base) && base>0, "Must supply base= as a finite positive number.")
3577    assert(is_matrix(data)||is_function(data), "data= must be a function literal, or contain a 2D array of numbers.")
3578    let(
3579        xvals = is_list(data)? [for (x = idx(data[0])) x] :
3580            is_range(xrange)? [for (x = xrange) x] :
3581            assert(false, "xrange= must be given as a range if data= is a function literal."),
3582        yvals = is_list(data)? [for (y = idx(data)) y] :
3583            is_range(yrange)? [for (y = yrange) y] :
3584            assert(false, "yrange= must be given as a range if data= is a function literal."),
3585        xlen = len(xvals),
3586        ylen = len(yvals),
3587        stepy = l / (ylen-1),
3588        stepx = stepy * aspect,
3589        maxr = max(r1,r2),
3590        circ = 2 * PI * maxr,
3591        astep = 360 / circ * stepx,
3592        arc = astep * (xlen-1),
3593        bsteps = round(segs(maxr-base) * arc / 360),
3594        bstep = arc / bsteps
3595    )
3596    assert(stepx*xlen <= circ, str("heightfield (",xlen," x ",ylen,") needs a radius of at least ",maxr*stepx*xlen/circ))
3597    let(
3598        verts = [
3599            for (yi = idx(yvals)) let(
3600                z = yi * stepy - l/2,
3601                rr = lerp(r1, r2, yi/(ylen-1))
3602            ) [
3603                cylindrical_to_xyz(rr-base, -arc/2, z),
3604                for (xi = idx(xvals)) let( a = xi*astep )
3605                    let(
3606                        rad = transpose? (
3607                                is_list(data)? data[xi][yi] : data(yvals[yi],xvals[xi])
3608                            ) : (
3609                                is_list(data)? data[yi][xi] : data(xvals[xi],yvals[yi])
3610                            ),
3611                        rad2 = constrain(rad, 0.01-base, maxh)
3612                    )
3613                    cylindrical_to_xyz(rr+rad2, a-arc/2, z),
3614                cylindrical_to_xyz(rr-base, arc/2, z),
3615                for (b = [1:1:bsteps-1]) let( a = arc/2-b*bstep )
3616                    cylindrical_to_xyz((z>0?r2:r1)-base, a, l/2*(z>0?1:-1)),
3617            ]
3618        ],
3619        vnf = vnf_vertex_array(verts, caps=true, col_wrap=true, reverse=true, style=style)
3620    ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
3621
3622
3623module cylindrical_heightfield(
3624    data, l, r, base=1,
3625    transpose=false, aspect=1,
3626    style="min_edge", convexity=10,
3627    xrange=[-1:0.01:1], yrange=[-1:0.01:1],
3628    maxh=99, r1, r2, d, d1, d2, h, height, length,
3629    anchor=CTR, spin=0, orient=UP
3630) {
3631    l = one_defined([l, h, height, length], "l,h,height,length");
3632    r1 = get_radius(r1=r1, r=r, d1=d1, d=d);
3633    r2 = get_radius(r1=r2, r=r, d1=d2, d=d);
3634    vnf = cylindrical_heightfield(
3635        data, l=l, r1=r1, r2=r2, base=base,
3636        xrange=xrange, yrange=yrange,
3637        maxh=maxh, transpose=transpose,
3638        aspect=aspect, style=style
3639    );
3640    attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
3641        vnf_polyhedron(vnf, convexity=convexity);
3642        children();
3643    }
3644}
3645
3646
3647// Module: ruler()
3648// Synopsis: Creates a ruler.
3649// SynTags: Geom
3650// Topics: Distance
3651// Usage:
3652//   ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]) [ATTACHMENTS];
3653// Description:
3654//   Creates an attachable ruler for checking dimensions of the model.
3655// Arguments:
3656//   length = length of the ruler.  Default 100
3657//   width = width of the ruler.  Default: size of the largest unit division
3658//   ---
3659//   thickness = thickness of the ruler. Default: 1
3660//   depth = the depth of mark subdivisions. Default: 3
3661//   labels = draw numeric labels for depths where labels are larger than 1.  Default: false
3662//   pipscale = width scale of the pips relative to the next size up.  Default: 1/3
3663//   maxscale = log10 of the maximum width divisions to display.  Default: based on input length
3664//   colors = colors to use for the ruler, a list of two values.  Default: `["black","white"]`
3665//   alpha = transparency value.  Default: 1.0
3666//   unit = unit to mark.  Scales the ruler marks to a different length.  Default: 1
3667//   inch = set to true for a ruler scaled to inches (assuming base dimension is mm).  Default: false
3668//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `LEFT+BACK+TOP`
3669//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3670//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3671// Examples(2D,Big):
3672//   ruler(100,depth=3);
3673//   ruler(100,depth=3,labels=true);
3674//   ruler(27);
3675//   ruler(27,maxscale=0);
3676//   ruler(100,pipscale=3/4,depth=2);
3677//   ruler(100,width=2,depth=2);
3678// Example(2D,Big):  Metric vs Imperial
3679//   ruler(12,width=50,inch=true,labels=true,maxscale=0);
3680//   fwd(50)ruler(300,width=50,labels=true);
3681
3682module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
3683             colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
3684{
3685    inchfactor = 25.4;
3686    checks =
3687        assert(depth<=5, "Cannot render scales smaller than depth=5")
3688        assert(len(colors)==2, "colors must contain a list of exactly two colors.");
3689    length = inch ? inchfactor * length : length;
3690    unit = inch ? inchfactor*unit : unit;
3691    maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
3692    scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
3693    widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
3694    width = default(width, scales[0]);
3695    widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
3696    offsets = concat([0],cumsum(widths));
3697    attachable(anchor,spin,orient, size=[length,width,thickness]) {
3698        translate([-length/2, -width/2, 0])
3699        for(i=[0:1:len(scales)-1]) {
3700            count = ceil(length/scales[i]);
3701            fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
3702            back(offsets[i]) {
3703                xcopies(scales[i], n=count, sp=[0,0,0]) union() {
3704                    actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
3705                    color(colors[$idx%2], alpha=alpha) {
3706                        w = i>0 ? quantup(widths[i],1/1024) : widths[i];    // What is the i>0 test supposed to do here?
3707                        cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
3708                    }
3709                    mark =
3710                        i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
3711                        i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
3712                        $idx % 10 == 4 ? 1 :
3713                        $idx % 10 == 5 ? 0 : -1;
3714                    flip = 1-mark*2;
3715                    if (mark >= 0) {
3716                        marklength = min(widths[i]/2, scales[i]*2);
3717                        markwidth = marklength*0.4;
3718                        translate([mark*scales[i], widths[i], 0]) {
3719                            color(colors[1-$idx%2], alpha=alpha) {
3720                                linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3721                                    polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
3722                                }
3723                            }
3724                        }
3725                    }
3726                    if (labels && scales[i]/unit+EPSILON >= 1) {
3727                        color(colors[($idx+1)%2], alpha=alpha) {
3728                            linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3729                                back(scales[i]*.02) {
3730                                    text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
3731                                }
3732                            }
3733                        }
3734                    }
3735
3736                }
3737            }
3738        }
3739        children();
3740    }
3741}
3742
3743
3744
3745
3746
3747// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap